From 2576cd1d8eb01f1d6f021e908cc410549d1d102f Mon Sep 17 00:00:00 2001 From: Hsun Lai Date: Mon, 21 Jul 2025 15:10:40 +0800 Subject: [PATCH] add community support for MediaTek Filogic device (BananaPi R4 Router) --- config/boards/bananapir4.csc | 18 + config/kernel/linux-filogic-current.config | 642 +++ config/sources/families/filogic.conf | 63 + .../firmware/mediatek/mt7988/mt7988_wo_0.bin | Bin 0 -> 107816 bytes .../firmware/mediatek/mt7988/mt7988_wo_1.bin | Bin 0 -> 107752 bytes packages/blobs/filogic/gpt | Bin 0 -> 17408 bytes ...support-for-MediaTek-SPI-NAND-flash-.patch | 4024 +++++++++++++++++ ...03-mtd-mtk-snand-add-support-for-SPL.patch | 175 + ...v-add-support-for-generic-MTD-device.patch | 390 ++ ...d-add-a-new-mtd-device-type-for-NMBM.patch | 44 + ...6-mtd-add-core-facility-code-of-NMBM.patch | 3533 +++++++++++++++ .../100-07-mtd-nmbm-add-support-for-mtd.patch | 958 ++++ ...dd-support-to-initialize-NMBM-after-.patch | 46 + .../100-09-cmd-add-nmbm-command.patch | 370 ++ ...-markbad-subcommand-for-NMBM-testing.patch | 80 + ...add-support-for-NMBM-upper-MTD-layer.patch | 260 ++ ...d-mtk-snand-add-NMBM-support-for-SPL.patch | 173 + ...new-command-for-NAND-flash-debugging.patch | 1118 +++++ ...-add-support-to-read-flash-unique-ID.patch | 142 + ...-add-support-to-read-flash-unique-ID.patch | 49 + ...and-enable-CONFIG_SYS_NAND_U_BOOT_OF.patch | 28 + ...00-21-mtd-spi-nor-add-more-flash-ids.patch | 77 + ...i-nand-backport-from-upstream-kernel.patch | 550 +++ ...support-to-display-verbose-error-log.patch | 78 + ...olume-find-create-remove-APIs-public.patch | 58 + ...creating-volume-with-all-free-spaces.patch | 27 + ...ort-to-create-environment-volume-if-.patch | 71 + ...ort-for-UBI-end-of-filesystem-marker.patch | 66 + ...-board-mediatek-wire-up-NMBM-support.patch | 235 + ...d-add-support-for-FORESEE-F35SQA002G.patch | 149 + ...d-add-support-for-FORESEE-F35SQA001G.patch | 38 + ...-mtd-spinand-fix-support-for-FORESEE.patch | 19 + .../103-04-mt7988-enable-pstore.patch | 33 + ...figs-add-usefull-stuff-to-mt7988-rfb.patch | 289 ++ .../120-use-xz-instead-of-lzma.patch | 11 + .../130-fix-mkimage-host-build.patch | 24 + ...pport-for-Airoha-ethernet-PHY-driver.patch | 1929 ++++++++ ...permit-to-select-bootmenu-entry-with.patch | 261 ++ .../200-cmd-add-imsz-and-imszb.patch | 130 + .../211-cmd-bootmenu-custom-title.patch | 33 + .../u-boot-filogic/220-cmd-env-readmem.patch | 116 + .../230-cmd-add-pstore-check.patch | 78 + .../250-fix-mmc-erase-timeout.patch | 11 + ...-of-FIT-configuration-in-chosen-node.patch | 31 + ...7988-generic-reset-button-ignore-env.patch | 45 + .../310-mt7988-select-rootdisk.patch | 67 + .../341-mtd-spinand-Support-dosilicon.patch | 329 ++ .../342-mtd-spinand-Support-fmsh.patch | 290 ++ .../343-mtd-spinand-gsto-Add-code.patch | 189 + .../u-boot-filogic/450-add-bpi-r4.patch | 653 +++ .../500-add-distro-boot-support.patch | 29 + 51 files changed, 18029 insertions(+) create mode 100644 config/boards/bananapir4.csc create mode 100644 config/kernel/linux-filogic-current.config create mode 100644 config/sources/families/filogic.conf create mode 100644 packages/blobs/filogic/firmware/mediatek/mt7988/mt7988_wo_0.bin create mode 100644 packages/blobs/filogic/firmware/mediatek/mt7988/mt7988_wo_1.bin create mode 100644 packages/blobs/filogic/gpt create mode 100644 patch/u-boot/u-boot-filogic/100-02-drivers-mtd-add-support-for-MediaTek-SPI-NAND-flash-.patch create mode 100644 patch/u-boot/u-boot-filogic/100-03-mtd-mtk-snand-add-support-for-SPL.patch create mode 100644 patch/u-boot/u-boot-filogic/100-04-env-add-support-for-generic-MTD-device.patch create mode 100644 patch/u-boot/u-boot-filogic/100-05-mtd-add-a-new-mtd-device-type-for-NMBM.patch create mode 100644 patch/u-boot/u-boot-filogic/100-06-mtd-add-core-facility-code-of-NMBM.patch create mode 100644 patch/u-boot/u-boot-filogic/100-07-mtd-nmbm-add-support-for-mtd.patch create mode 100644 patch/u-boot/u-boot-filogic/100-08-common-board_r-add-support-to-initialize-NMBM-after-.patch create mode 100644 patch/u-boot/u-boot-filogic/100-09-cmd-add-nmbm-command.patch create mode 100644 patch/u-boot/u-boot-filogic/100-10-cmd-mtd-add-markbad-subcommand-for-NMBM-testing.patch create mode 100644 patch/u-boot/u-boot-filogic/100-11-env-add-support-for-NMBM-upper-MTD-layer.patch create mode 100644 patch/u-boot/u-boot-filogic/100-12-mtd-mtk-snand-add-NMBM-support-for-SPL.patch create mode 100644 patch/u-boot/u-boot-filogic/100-13-cmd-add-a-new-command-for-NAND-flash-debugging.patch create mode 100644 patch/u-boot/u-boot-filogic/100-14-mtd-spi-nor-add-support-to-read-flash-unique-ID.patch create mode 100644 patch/u-boot/u-boot-filogic/100-15-cmd-sf-add-support-to-read-flash-unique-ID.patch create mode 100644 patch/u-boot/u-boot-filogic/100-17-common-spl-spl_nand-enable-CONFIG_SYS_NAND_U_BOOT_OF.patch create mode 100644 patch/u-boot/u-boot-filogic/100-21-mtd-spi-nor-add-more-flash-ids.patch create mode 100644 patch/u-boot/u-boot-filogic/100-22-mtd-spi-nand-backport-from-upstream-kernel.patch create mode 100644 patch/u-boot/u-boot-filogic/100-23-mmc-mtk-sd-add-support-to-display-verbose-error-log.patch create mode 100644 patch/u-boot/u-boot-filogic/100-24-cmd-ubi-make-volume-find-create-remove-APIs-public.patch create mode 100644 patch/u-boot/u-boot-filogic/100-25-cmd-ubi-allow-creating-volume-with-all-free-spaces.patch create mode 100644 patch/u-boot/u-boot-filogic/100-26-env-ubi-add-support-to-create-environment-volume-if-.patch create mode 100644 patch/u-boot/u-boot-filogic/100-27-mtd-ubi-add-support-for-UBI-end-of-filesystem-marker.patch create mode 100644 patch/u-boot/u-boot-filogic/100-29-board-mediatek-wire-up-NMBM-support.patch create mode 100644 patch/u-boot/u-boot-filogic/101-01-mtd-spinand-add-support-for-FORESEE-F35SQA002G.patch create mode 100644 patch/u-boot/u-boot-filogic/101-02-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch create mode 100644 patch/u-boot/u-boot-filogic/101-03-mtd-spinand-fix-support-for-FORESEE.patch create mode 100644 patch/u-boot/u-boot-filogic/103-04-mt7988-enable-pstore.patch create mode 100644 patch/u-boot/u-boot-filogic/105-configs-add-usefull-stuff-to-mt7988-rfb.patch create mode 100644 patch/u-boot/u-boot-filogic/120-use-xz-instead-of-lzma.patch create mode 100644 patch/u-boot/u-boot-filogic/130-fix-mkimage-host-build.patch create mode 100644 patch/u-boot/u-boot-filogic/160-net-phy-add-support-for-Airoha-ethernet-PHY-driver.patch create mode 100644 patch/u-boot/u-boot-filogic/170-cmd-bootmenu-permit-to-select-bootmenu-entry-with.patch create mode 100644 patch/u-boot/u-boot-filogic/200-cmd-add-imsz-and-imszb.patch create mode 100644 patch/u-boot/u-boot-filogic/211-cmd-bootmenu-custom-title.patch create mode 100644 patch/u-boot/u-boot-filogic/220-cmd-env-readmem.patch create mode 100644 patch/u-boot/u-boot-filogic/230-cmd-add-pstore-check.patch create mode 100644 patch/u-boot/u-boot-filogic/250-fix-mmc-erase-timeout.patch create mode 100644 patch/u-boot/u-boot-filogic/280-image-fdt-save-name-of-FIT-configuration-in-chosen-node.patch create mode 100644 patch/u-boot/u-boot-filogic/305-mt7988-generic-reset-button-ignore-env.patch create mode 100644 patch/u-boot/u-boot-filogic/310-mt7988-select-rootdisk.patch create mode 100644 patch/u-boot/u-boot-filogic/341-mtd-spinand-Support-dosilicon.patch create mode 100644 patch/u-boot/u-boot-filogic/342-mtd-spinand-Support-fmsh.patch create mode 100644 patch/u-boot/u-boot-filogic/343-mtd-spinand-gsto-Add-code.patch create mode 100644 patch/u-boot/u-boot-filogic/450-add-bpi-r4.patch create mode 100644 patch/u-boot/u-boot-filogic/500-add-distro-boot-support.patch diff --git a/config/boards/bananapir4.csc b/config/boards/bananapir4.csc new file mode 100644 index 0000000000..6d77c06ee8 --- /dev/null +++ b/config/boards/bananapir4.csc @@ -0,0 +1,18 @@ +# Mediatek MT7988a quad core Cortex-A73 4/8GB RAM 8GB EMMC mPci USB3.0 4xGBE +BOARD_NAME="Banana Pi R4" +BOARDFAMILY="filogic" +BOARD_MAINTAINER="" +KERNEL_TARGET="current" +KERNEL_TEST_TARGET="current" +BOOTCONFIG="mt7988a_bananapi_bpi-r4-sdmmc_defconfig" +BOOT_FDT_FILE="mediatek/mt7988a-bananapi-bpi-r4-sd.dtb" +SRC_EXTLINUX="yes" +SRC_CMDLINE="console=ttyS0,115200n1 earlyprintk loglevel=8 initcall_debug=0 swiotlb=512 cgroup_enable cgroup_memory=1 init=/sbin/init" + +function post_family_tweaks__bpi-r4() { + display_alert "Applying eth blobs" + + mkdir -p "$SDCARD/lib/firmware/mediatek/mt7988" + cp -v "$SRC/packages/blobs/filogic/firmware/mediatek/mt7988/mt7988_wo_0.bin" "$SDCARD/lib/firmware/mediatek/mt7988/mt7988_wo_0.bin" + cp -v "$SRC/packages/blobs/filogic/firmware/mediatek/mt7988/mt7988_wo_1.bin" "$SDCARD/lib/firmware/mediatek/mt7988/mt7988_wo_1.bin" +} diff --git a/config/kernel/linux-filogic-current.config b/config/kernel/linux-filogic-current.config new file mode 100644 index 0000000000..e8fb9dff7c --- /dev/null +++ b/config/kernel/linux-filogic-current.config @@ -0,0 +1,642 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +# CONFIG_CROSS_MEMORY_ATTACH is not set +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_IRQ_TIME_ACCOUNTING=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +# CONFIG_CPU_ISOLATION is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_RDMA=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_BPF=y +CONFIG_NAMESPACES=y +# CONFIG_TIME_NS is not set +CONFIG_USER_NS=y +CONFIG_BLK_DEV_INITRD=y +# CONFIG_INITRAMFS_PRESERVE_MTIME is not set +CONFIG_EXPERT=y +# CONFIG_SYSFS_SYSCALL is not set +# CONFIG_RSEQ is not set +# CONFIG_CACHESTAT_SYSCALL is not set +CONFIG_ARCH_MEDIATEK=y +# CONFIG_AMPERE_ERRATUM_AC03_CPU_38 is not set +# CONFIG_ARM64_ERRATUM_826319 is not set +# CONFIG_ARM64_ERRATUM_827319 is not set +# CONFIG_ARM64_ERRATUM_824069 is not set +# CONFIG_ARM64_ERRATUM_819472 is not set +# CONFIG_ARM64_ERRATUM_832075 is not set +# CONFIG_ARM64_ERRATUM_1024718 is not set +# CONFIG_ARM64_ERRATUM_1165522 is not set +# CONFIG_ARM64_ERRATUM_1319367 is not set +# CONFIG_ARM64_ERRATUM_1530923 is not set +# CONFIG_ARM64_ERRATUM_1463225 is not set +# CONFIG_ARM64_ERRATUM_1508412 is not set +# CONFIG_ARM64_ERRATUM_2051678 is not set +# CONFIG_ARM64_ERRATUM_2077057 is not set +# CONFIG_ARM64_ERRATUM_2658417 is not set +# CONFIG_ARM64_ERRATUM_2054223 is not set +# CONFIG_ARM64_ERRATUM_2067961 is not set +# CONFIG_ARM64_ERRATUM_2645198 is not set +# CONFIG_ARM64_ERRATUM_2966298 is not set +# CONFIG_ARM64_ERRATUM_3117295 is not set +# CONFIG_ARM64_ERRATUM_3194386 is not set +# CONFIG_CAVIUM_ERRATUM_22375 is not set +# CONFIG_CAVIUM_ERRATUM_23154 is not set +# CONFIG_CAVIUM_ERRATUM_27456 is not set +# CONFIG_CAVIUM_ERRATUM_30115 is not set +# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set +# CONFIG_FUJITSU_ERRATUM_010001 is not set +# CONFIG_HISILICON_ERRATUM_161600802 is not set +# CONFIG_HISILICON_ERRATUM_162100801 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set +# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set +# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set +# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set +# CONFIG_ROCKCHIP_ERRATUM_3588001 is not set +# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set +CONFIG_ARM64_VA_BITS_48=y +CONFIG_SCHED_MC=y +CONFIG_NR_CPUS=4 +CONFIG_HZ_100=y +# CONFIG_UNMAP_KERNEL_AT_EL0 is not set +CONFIG_ARM64_SW_TTBR0_PAN=y +# CONFIG_ARM64_HW_AFDBM is not set +# CONFIG_ARM64_USE_LSE_ATOMICS is not set +# CONFIG_ARM64_RAS_EXTN is not set +# CONFIG_ARM64_PTR_AUTH is not set +# CONFIG_ARM64_AMU_EXTN is not set +# CONFIG_ARM64_TLB_RANGE is not set +# CONFIG_ARM64_BTI is not set +# CONFIG_ARM64_E0PD is not set +# CONFIG_ARM64_MTE is not set +# CONFIG_ARM64_EPAN is not set +# CONFIG_ARM64_POE is not set +# CONFIG_ARM64_SVE is not set +# CONFIG_RELOCATABLE is not set +CONFIG_CMDLINE_OVERRIDE=y +# CONFIG_SUSPEND is not set +CONFIG_PM=y +CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y +CONFIG_CPU_IDLE=y +CONFIG_ARM_PSCI_CPUIDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_ARM_MEDIATEK_CPUFREQ=y +# CONFIG_ARM_MEDIATEK_CPUFREQ_HW is not set +CONFIG_JUMP_LABEL=y +# CONFIG_STACKPROTECTOR_STRONG is not set +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_THROTTLING=y +# CONFIG_BLK_DEBUG_FS is not set +CONFIG_PARTITION_ADVANCED=y +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set +# CONFIG_IOSCHED_BFQ is not set +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_ZSWAP=y +CONFIG_ZSWAP_ZPOOL_DEFAULT_ZBUD=y +CONFIG_SLAB_FREELIST_RANDOM=y +CONFIG_SLAB_FREELIST_HARDENED=y +# CONFIG_COMPAT_BRK is not set +# CONFIG_VM_EVENT_COUNTERS is not set +# CONFIG_SECRETMEM is not set +CONFIG_LRU_GEN=y +CONFIG_LRU_GEN_ENABLED=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_XFRM_USER=m +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_INET_ESP=m +# CONFIG_INET_DIAG is not set +CONFIG_TCP_CONG_ADVANCED=y +# CONFIG_TCP_CONG_BIC is not set +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_HTCP is not set +# CONFIG_IPV6_SIT is not set +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +# CONFIG_NETFILTER_EGRESS is not set +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_MARK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +CONFIG_NF_CONNTRACK_LABELS=y +# CONFIG_NF_CT_PROTO_DCCP is not set +# CONFIG_NF_CT_PROTO_SCTP is not set +# CONFIG_NF_CT_PROTO_UDPLITE is not set +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_NUMGEN=m +CONFIG_NFT_CT=m +CONFIG_NFT_FLOW_OFFLOAD=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_TUNNEL=m +CONFIG_NFT_QUOTA=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NFT_HASH=m +CONFIG_NFT_FIB_INET=m +CONFIG_NFT_XFRM=m +CONFIG_NFT_SOCKET=m +CONFIG_NFT_TPROXY=m +CONFIG_NFT_SYNPROXY=m +CONFIG_NFT_DUP_NETDEV=m +CONFIG_NFT_FWD_NETDEV=m +CONFIG_NFT_REJECT_NETDEV=m +CONFIG_NF_FLOW_TABLE_INET=m +CONFIG_NF_FLOW_TABLE=m +CONFIG_NETFILTER_XT_MARK=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_IP_VS=m +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_NFCT=y +CONFIG_NFT_FIB_IPV4=m +CONFIG_NF_TABLES_ARP=y +CONFIG_NF_LOG_IPV4=m +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_RAW=m +CONFIG_NFT_FIB_IPV6=m +CONFIG_NF_LOG_IPV6=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_NET_DSA=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_FQ_CODEL=y +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_DEFAULT=y +CONFIG_DEFAULT_FQ_CODEL=y +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_CLS_BPF=m +CONFIG_NET_CLS_FLOWER=m +CONFIG_NET_CLS_MATCHALL=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_VLAN=m +CONFIG_NET_ACT_BPF=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_BT=y +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_MAC80211_LEDS=y +CONFIG_RFKILL_FULL=y +CONFIG_RFKILL_GPIO=y +# CONFIG_LWTUNNEL_BPF is not set +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIEAER=y +CONFIG_PCIEASPM_PERFORMANCE=y +CONFIG_PCI_DEBUG=y +# CONFIG_VGA_ARB is not set +CONFIG_PCIE_MEDIATEK_GEN3=y +CONFIG_UEVENT_HELPER=y +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_FW_LOADER_USER_HELPER=y +CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y +# CONFIG_ARM_SMCCC_SOC_ID is not set +CONFIG_MTD=y +CONFIG_MTD_PARSER_TRX=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_CFI=y +CONFIG_MTD_CFI_INTELEXT=y +CONFIG_MTD_CFI_AMDSTD=y +CONFIG_MTD_COMPLEX_MAPPINGS=y +CONFIG_MTD_RAW_NAND=y +CONFIG_MTD_NAND_MTK=y +CONFIG_MTD_SPI_NAND=y +CONFIG_MTD_NAND_ECC_MEDIATEK=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_FASTMAP=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_MTD_UBI_NVMEM=y +CONFIG_OF_OVERLAY=y +CONFIG_ZRAM=y +CONFIG_ZRAM_BACKEND_LZ4=y +CONFIG_ZRAM_BACKEND_LZ4HC=y +CONFIG_ZRAM_BACKEND_ZSTD=y +CONFIG_ZRAM_BACKEND_DEFLATE=y +CONFIG_ZRAM_BACKEND_842=y +CONFIG_ZRAM_BACKEND_LZO=y +CONFIG_ZRAM_DEF_COMP_ZSTD=y +CONFIG_ZRAM_WRITEBACK=y +CONFIG_ZRAM_MEMORY_TRACKING=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_NVME=y +CONFIG_EEPROM_AT24=m +CONFIG_BLK_DEV_SD=y +# CONFIG_BLK_DEV_BSG is not set +CONFIG_ATA=y +# CONFIG_ATA_VERBOSE_ERROR is not set +# CONFIG_ATA_FORCE is not set +CONFIG_NETDEVICES=y +CONFIG_DUMMY=m +CONFIG_MACVLAN=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_VETH=m +CONFIG_NETKIT=y +CONFIG_NET_DSA_AN8855=y +CONFIG_NET_DSA_MT7530=y +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_LITEX is not set +CONFIG_NET_VENDOR_MEDIATEK=y +CONFIG_NET_MEDIATEK_SOC=y +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +CONFIG_SFP=m +CONFIG_AIROHA_EN8801SC_PHY=y +CONFIG_AIR_AN8855_PHY=y +CONFIG_AQUANTIA_PHY=m +CONFIG_ICPLUS_PHY=y +CONFIG_MAXLINEAR_GPHY=y +CONFIG_MEDIATEK_2P5GE_PHY=y +CONFIG_REALTEK_PHY=y +CONFIG_MDIO_AN8855=y +CONFIG_PPP=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=m +CONFIG_PPP_ASYNC=m +# CONFIG_WLAN_VENDOR_ADMTEK is not set +# CONFIG_WLAN_VENDOR_ATH is not set +CONFIG_AT76C50X_USB=m +# CONFIG_WLAN_VENDOR_BROADCOM is not set +# CONFIG_WLAN_VENDOR_INTEL is not set +# CONFIG_WLAN_VENDOR_INTERSIL is not set +# CONFIG_WLAN_VENDOR_MARVELL is not set +CONFIG_MT7601U=m +CONFIG_MT76x0U=m +CONFIG_MT76x2U=m +CONFIG_MT7663U=m +CONFIG_MT7915E=m +CONFIG_MT7921E=m +CONFIG_MT7921U=m +CONFIG_MT7996E=m +CONFIG_MT7925E=m +CONFIG_MT7925U=m +# CONFIG_WLAN_VENDOR_MICROCHIP is not set +# CONFIG_WLAN_VENDOR_PURELIFI is not set +# CONFIG_WLAN_VENDOR_RALINK is not set +CONFIG_RTL8XXXU=m +CONFIG_RTW88=m +CONFIG_RTW88_8822BE=m +CONFIG_RTW88_8822BU=m +CONFIG_RTW88_8822CE=m +CONFIG_RTW88_8822CU=m +CONFIG_RTW88_8723DU=m +CONFIG_RTW88_8821CE=m +CONFIG_RTW88_8821CU=m +CONFIG_RTW89=m +CONFIG_RTW89_8852AE=m +CONFIG_RTW89_8852BE=m +CONFIG_RTW89_8852CE=m +CONFIG_RTW89_8922AE=m +# CONFIG_WLAN_VENDOR_RSI is not set +# CONFIG_WLAN_VENDOR_SILABS is not set +# CONFIG_WLAN_VENDOR_ST is not set +# CONFIG_WLAN_VENDOR_TI is not set +CONFIG_RTL8822BU=m +CONFIG_RTL8821CU=m +# CONFIG_WLAN_VENDOR_ZYDAS is not set +# CONFIG_WLAN_VENDOR_QUANTENNA is not set +CONFIG_ISDN=y +# CONFIG_INPUT is not set +# CONFIG_SERIO is not set +# CONFIG_VT is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LEGACY_TIOCSTI is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_PCI is not set +# CONFIG_SERIAL_8250_EXAR is not set +CONFIG_SERIAL_8250_NR_UARTS=3 +CONFIG_SERIAL_8250_RUNTIME_UARTS=3 +CONFIG_SERIAL_8250_MT6577=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_DEV_BUS=y +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set +# CONFIG_DEVMEM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MUX=m +CONFIG_I2C_MUX_PCA954x=m +# CONFIG_I2C_HELPER_AUTO is not set +CONFIG_I2C_MT65XX=y +CONFIG_SPI=y +CONFIG_SPI_MT65XX=y +CONFIG_SPI_MTK_SNFI=y +# CONFIG_PTP_1588_CLOCK is not set +# CONFIG_PINCTRL_MT2712 is not set +# CONFIG_PINCTRL_MT6765 is not set +# CONFIG_PINCTRL_MT6779 is not set +# CONFIG_PINCTRL_MT6795 is not set +# CONFIG_PINCTRL_MT6797 is not set +# CONFIG_PINCTRL_MT7622 is not set +# CONFIG_PINCTRL_MT8167 is not set +# CONFIG_PINCTRL_MT8173 is not set +# CONFIG_PINCTRL_MT8183 is not set +# CONFIG_PINCTRL_MT8186 is not set +# CONFIG_PINCTRL_MT8188 is not set +# CONFIG_PINCTRL_MT8192 is not set +# CONFIG_PINCTRL_MT8195 is not set +# CONFIG_PINCTRL_MT8365 is not set +# CONFIG_PINCTRL_MT8516 is not set +CONFIG_GPIO_SYSFS=y +# CONFIG_GPIO_CDEV_V1 is not set +CONFIG_POWER_RESET_SYSCON=y +# CONFIG_POWER_SUPPLY_HWMON is not set +CONFIG_SENSORS_PWM_FAN=m +CONFIG_THERMAL=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_BANG_BANG=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +# CONFIG_CPU_FREQ_THERMAL is not set +CONFIG_MTK_THERMAL=y +CONFIG_MTK_SOC_THERMAL=y +CONFIG_MTK_LVTS_THERMAL=y +CONFIG_MTK_LVTS_THERMAL_DEBUGFS=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +# CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP is not set +CONFIG_GPIO_WATCHDOG=y +CONFIG_GPIO_WATCHDOG_ARCH_INITCALL=y +CONFIG_MFD_AIROHA_AN8855=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_MT6380=y +CONFIG_REGULATOR_RT5190A=y +CONFIG_SOUND=m +CONFIG_SND=m +CONFIG_USB=m +CONFIG_USB_XHCI_HCD=m +CONFIG_USB_XHCI_PLATFORM=m +CONFIG_USB_XHCI_MTK=m +CONFIG_USB_STORAGE=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_UAS=m +CONFIG_MMC=y +CONFIG_MMC_MTK=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CLASS_MULTICOLOR=y +CONFIG_LEDS_BRIGHTNESS_HW_CHANGED=y +CONFIG_LEDS_GPIO=m +CONFIG_LEDS_PWM=y +CONFIG_LEDS_SMARTRG_LED=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_NETDEV=y +CONFIG_LEDS_TRIGGER_PATTERN=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_NVMEM is not set +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_MT7622=y +CONFIG_DMADEVICES=y +CONFIG_MTK_HSDMA=y +CONFIG_DMATEST=y +CONFIG_STAGING=y +# CONFIG_SURFACE_PLATFORMS is not set +# CONFIG_COMMON_CLK_MT2712 is not set +# CONFIG_COMMON_CLK_MT6765 is not set +# CONFIG_COMMON_CLK_MT6779 is not set +# CONFIG_COMMON_CLK_MT6795 is not set +# CONFIG_COMMON_CLK_MT6797 is not set +# CONFIG_COMMON_CLK_MT7622 is not set +# CONFIG_COMMON_CLK_MT8167 is not set +# CONFIG_COMMON_CLK_MT8173 is not set +# CONFIG_COMMON_CLK_MT8183 is not set +# CONFIG_COMMON_CLK_MT8186 is not set +# CONFIG_COMMON_CLK_MT8188 is not set +# CONFIG_COMMON_CLK_MT8192 is not set +# CONFIG_COMMON_CLK_MT8195 is not set +# CONFIG_COMMON_CLK_MT8365 is not set +# CONFIG_COMMON_CLK_MT8516 is not set +# CONFIG_FSL_ERRATUM_A008585 is not set +# CONFIG_HISILICON_ERRATUM_161010101 is not set +# CONFIG_ARM64_ERRATUM_858921 is not set +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MTK_PMIC_WRAP=y +# CONFIG_MTK_MMSYS is not set +# CONFIG_MTK_SOCINFO is not set +CONFIG_PM_DEVFREQ=y +CONFIG_ARM_MEDIATEK_CCI_DEVFREQ=y +CONFIG_PM_DEVFREQ_EVENT=y +CONFIG_PWM=y +CONFIG_PWM_MEDIATEK=y +# CONFIG_MST_IRQ is not set +CONFIG_RESET_TI_SYSCON=y +CONFIG_PHY_MTK_XFI_TPHY=y +CONFIG_PHY_MTK_TPHY=y +CONFIG_PHY_MTK_XSPHY=y +CONFIG_NVMEM_LAYOUT_ADTRAN=y +CONFIG_NVMEM_AN8855_EFUSE=y +CONFIG_NVMEM_MTK_EFUSE=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_XFS_FS=y +CONFIG_BTRFS_FS=y +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +# CONFIG_F2FS_IOSTAT is not set +# CONFIG_DNOTIFY is not set +CONFIG_FANOTIFY=y +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=y +CONFIG_CUSE=y +CONFIG_VIRTIO_FS=m +CONFIG_OVERLAY_FS=y +CONFIG_OVERLAY_FS_XINO_AUTO=y +CONFIG_ISO9660_FS=y +CONFIG_UDF_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_EXFAT_FS=y +CONFIG_NTFS3_FS_POSIX_ACL=y +CONFIG_NTFS_FS=y +# CONFIG_PROC_PAGE_MONITOR is not set +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_CONFIGFS_FS=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_SUMMARY=y +CONFIG_JFFS2_FS_XATTR=y +CONFIG_JFFS2_COMPRESSION_OPTIONS=y +# CONFIG_JFFS2_ZLIB is not set +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_FILE_DIRECT=y +CONFIG_SQUASHFS_COMPILE_DECOMP_MULTI_PERCPU=y +# CONFIG_SQUASHFS_ZLIB is not set +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_EMBEDDED=y +CONFIG_PSTORE=y +CONFIG_PSTORE_CONSOLE=y +CONFIG_PSTORE_PMSG=y +CONFIG_PSTORE_RAM=y +CONFIG_EROFS_FS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V4=y +CONFIG_NFSD=y +CONFIG_CIFS=y +CONFIG_SMB_SERVER=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PERSISTENT_KEYRINGS=y +CONFIG_ENCRYPTED_KEYS=y +CONFIG_KEY_DH_OPERATIONS=y +CONFIG_SECURITY_DMESG_RESTRICT=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_FORTIFY_SOURCE=y +CONFIG_LSM="lockdown,yama,loadpin,safesetid,integrity" +CONFIG_INIT_STACK_NONE=y +CONFIG_LIST_HARDENED=y +CONFIG_CRYPTO_USER=m +CONFIG_CRYPTO_PCRYPT=y +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_DES=m +CONFIG_CRYPTO_ARC4=m +CONFIG_CRYPTO_USER_API_HASH=m +CONFIG_CRYPTO_USER_API_SKCIPHER=m +CONFIG_CRYPTO_USER_API_RNG=m +CONFIG_CRYPTO_USER_API_AEAD=m +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_SHA1_ARM64_CE=m +CONFIG_CRYPTO_SHA2_ARM64_CE=y +CONFIG_CRYPTO_SHA512_ARM64=m +CONFIG_CRYPTO_AES_ARM64=y +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +CONFIG_CRYPTO_SM4_ARM64_CE_CCM=y +CONFIG_CRYPTO_SM4_ARM64_CE_GCM=y +CONFIG_CRYPTO_DEV_SAFEXCEL=m +CONFIG_CRC_CCITT=y +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_ARM is not set +# CONFIG_XZ_DEC_ARMTHUMB is not set +# CONFIG_XZ_DEC_ARM64 is not set +# CONFIG_XZ_DEC_SPARC is not set +# CONFIG_XZ_DEC_RISCV is not set +CONFIG_PRINTK_TIME=y +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=15 +CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7 +# CONFIG_DEBUG_BUGVERBOSE is not set +CONFIG_DEBUG_INFO_DWARF5=y +CONFIG_DEBUG_INFO_REDUCED=y +CONFIG_STRIP_ASM_SYMS=y +# CONFIG_SECTION_MISMATCH_WARN_ONLY is not set +CONFIG_MAGIC_SYSRQ=y +# CONFIG_MAGIC_SYSRQ_SERIAL is not set +CONFIG_DEBUG_FS=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_SCHED_STACK_END_CHECK=y +CONFIG_PANIC_ON_OOPS=y +CONFIG_PANIC_TIMEOUT=1 +# CONFIG_SCHED_DEBUG is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=60 +# CONFIG_RCU_TRACE is not set +# CONFIG_FTRACE is not set diff --git a/config/sources/families/filogic.conf b/config/sources/families/filogic.conf new file mode 100644 index 0000000000..e9844a69e2 --- /dev/null +++ b/config/sources/families/filogic.conf @@ -0,0 +1,63 @@ +# +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2013-2023 Igor Pecovnik, igor@armbian.com +# +# This file is a part of the Armbian Build Framework +# https://github.com/armbian/build/ +# +declare -g ARCH=arm64 +declare -g OFFSET=16 +declare -g ATFSOURCE='https://github.com/mtk-openwrt/arm-trusted-firmware.git' +declare -g ATF_TARGET_MAP="PLAT=mt7988 BOOT_DEVICE=sdmmc USE_MKIMAGE=1 BOARD_BGA=1 HAVE_DRAM_OBJ_FILE=yes DRAM_USE_COMB=1 all;;build/mt7988/release/bl2.img build/mt7988/release/bl31.bin" +declare -g ATFBRANCH="branch:mtksoc-20250212" +declare -g BOOTDELAY=1 +declare -g BOOTPATCHDIR="${BOOTPATCHDIR:-"u-boot-filogic"}" +declare -g BOOTBRANCH="${BOOTBRANCH:-"tag:v2025.04"}" +declare -g BOOTENV_FILE='filogic.txt' +declare -g UBOOT_TARGET_MAP=";;u-boot.bin" +declare -g BOOTSCRIPT='boot-filogic.cmd:boot.cmd' +declare -g IMAGE_PARTITION_TABLE="gpt" +declare -g LINUXFAMILY=filogic + +# This build requires xxd +function add_host_dependencies__filogic_add_xxd_hostdep() { + display_alert "Adding xxd dep" "for ${BOARD} bootloader compile" "debug" + declare -g EXTRA_BUILD_DEPS="${EXTRA_BUILD_DEPS} xxd arm-trusted-firmware-tools" +} + +# Handling of FIP blobs +function uboot_custom_postprocess() { + run_host_command_logged rm -f "$SRC"/cache/u-boot_sdmmc.fip + run_host_command_logged fiptool create \ + --soc-fw "$SRC"/cache/sources/arm-trusted-firmware/mtksoc-20250212/build/mt7988/release/bl31.bin \ + --nt-fw "$SRC"/cache/sources/u-boot-worktree/u-boot/v2025.04/u-boot.bin \ + "$SRC"/cache/u-boot_sdmmc.fip +} + +case $BRANCH in + + current) + KERNELSOURCE='https://github.com/chainsx/linux-filogic.git' + KERNELBRANCH="branch:linux-6.12.35" + declare -g KERNEL_MAJOR_MINOR="6.12" + KERNELPATCHDIR="${LINUXFAMILY}-${BRANCH}" + LINUXCONFIG="linux-${LINUXFAMILY}-${BRANCH}" + + ;; +esac + +#KERNELPATCHDIR="archive/filogic-${KERNEL_MAJOR_MINOR}" + +write_uboot_platform() { + dd if="$SRC"/cache/sources/arm-trusted-firmware/mtksoc-20250212/build/mt7988/release/bl2.img of=$2 bs=512 seek=34 status=noxfer > /dev/null 2>&1 + dd if="$SRC"/cache/u-boot_sdmmc.fip of=$2 bs=512 seek=13312 status=noxfer >/dev/null 2>&1 + + # backup gpt table + LAST_START=$(parted "$2" unit s print | grep -v "^$" | tail -n 1 | awk '{print $2}' | tr -d 's') + LAST_SIZE=$(parted "$2" unit s print | grep -v "^$" | tail -n 1 | awk '{print $4}' | tr -d 's') + # write mtk gpt table + dd if="${SRC}/packages/blobs/filogic/gpt" of="$2" conv=notrunc + # append armbian rootfs info + echo "${LAST_START},${LAST_SIZE}" | sfdisk --no-reread --append "$2" +} diff --git a/packages/blobs/filogic/firmware/mediatek/mt7988/mt7988_wo_0.bin b/packages/blobs/filogic/firmware/mediatek/mt7988/mt7988_wo_0.bin new file mode 100644 index 0000000000000000000000000000000000000000..6e44614614913e8254bdd95b5447ec4fa58b9a75 GIT binary patch literal 107816 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∋*#53{=sZF~A~AYXW|TG#fU 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 z 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 literal 0 HcmV?d00001 diff --git a/packages/blobs/filogic/gpt b/packages/blobs/filogic/gpt new file mode 100644 index 0000000000000000000000000000000000000000..04444e0cf02f0fa677a26983bf8c4f84b674922a GIT binary patch literal 17408 zcmeIwF-yZh7{>7@sV*+kPtaOLq@&eIDhO&22c@=j(8Y>{f>^0m=^zwzaO&>n+{N8a zE<#r)KY+8dTfO6w!7RnTYaqrFYi~ zMak2FDNvs8C2SpA(T=EU#=S~y_pnng6qA|UdZ~JKci-*F57+Ioy_cK8zL^+fF7TLt znZCJg1f5pC_7+^f$IjJ@&V7(_jZB99RCJ=nKj>{pzOS?k8O*vl`Y)TRsBW$c9qUw= zBY(+|{DkJh{M`5ast$Ez((7udHFCPNaGrfmeZD52w)N0o?XSfAC$tpi@5S?LI{QT~ zTM +Date: Tue, 2 Mar 2021 16:58:01 +0800 +Subject: [PATCH 36/71] drivers: mtd: add support for MediaTek SPI-NAND flash + controller + +Add mtd driver for MediaTek SPI-NAND flash controller + +This driver is written from scratch, and uses standard mtd framework, not +the nand framework which only applies for raw parallel nand flashes so that +this driver can have a smaller size in binary. + +Signed-off-by: Weijie Gao +--- + drivers/mtd/Kconfig | 2 + + drivers/mtd/Makefile | 2 + + drivers/mtd/mtk-snand/Kconfig | 21 + + drivers/mtd/mtk-snand/Makefile | 11 + + drivers/mtd/mtk-snand/mtk-snand-def.h | 271 ++++ + drivers/mtd/mtk-snand/mtk-snand-ecc.c | 411 ++++++ + drivers/mtd/mtk-snand/mtk-snand-ids.c | 515 +++++++ + drivers/mtd/mtk-snand/mtk-snand-mtd.c | 535 +++++++ + drivers/mtd/mtk-snand/mtk-snand-os.c | 39 + + drivers/mtd/mtk-snand/mtk-snand-os.h | 120 ++ + drivers/mtd/mtk-snand/mtk-snand.c | 1933 +++++++++++++++++++++++++ + drivers/mtd/mtk-snand/mtk-snand.h | 77 + + 12 files changed, 3937 insertions(+) + create mode 100644 drivers/mtd/mtk-snand/Kconfig + create mode 100644 drivers/mtd/mtk-snand/Makefile + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-def.h + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-ecc.c + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-ids.c + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-mtd.c + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-os.c + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-os.h + create mode 100644 drivers/mtd/mtk-snand/mtk-snand.c + create mode 100644 drivers/mtd/mtk-snand/mtk-snand.h + +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -246,6 +246,8 @@ config SYS_MAX_FLASH_BANKS_DETECT + to reduce the effective number of flash bank, between 0 and + CONFIG_SYS_MAX_FLASH_BANKS + ++source "drivers/mtd/mtk-snand/Kconfig" ++ + source "drivers/mtd/nand/Kconfig" + + config SYS_NAND_MAX_OOBFREE +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -40,3 +40,5 @@ obj-$(CONFIG_$(PHASE_)SPI_FLASH_SUPPORT) + obj-$(CONFIG_SPL_UBI) += ubispl/ + + endif ++ ++obj-$(CONFIG_MTK_SPI_NAND) += mtk-snand/ +--- a/drivers/mtd/mtk-snand/Kconfig ++++ b/drivers/mtd/mtk-snand/Kconfig +@@ -0,0 +1,21 @@ ++# ++# Copyright (C) 2020 MediaTek Inc. All rights reserved. ++# Author: Weijie Gao ++# ++# SPDX-License-Identifier: GPL-2.0 ++# ++ ++config MTK_SPI_NAND ++ tristate "MediaTek SPI NAND flash controller driver" ++ depends on !MTD_SPI_NAND ++ help ++ This option enables access to SPI-NAND flashes through the ++ MediaTek SPI NAND Flash Controller ++ ++config MTK_SPI_NAND_MTD ++ tristate "MTD support for MediaTek SPI NAND flash controller" ++ depends on DM_MTD ++ depends on MTK_SPI_NAND ++ help ++ This option enables access to SPI-NAND flashes through the ++ MTD interface of MediaTek SPI NAND Flash Controller +--- a/drivers/mtd/mtk-snand/Makefile ++++ b/drivers/mtd/mtk-snand/Makefile +@@ -0,0 +1,11 @@ ++# ++# Copyright (C) 2020 MediaTek Inc. All rights reserved. ++# Author: Weijie Gao ++# ++# SPDX-License-Identifier: GPL-2.0 ++# ++ ++obj-y += mtk-snand.o mtk-snand-ecc.o mtk-snand-ids.o mtk-snand-os.o ++obj-$(CONFIG_MTK_SPI_NAND_MTD) += mtk-snand-mtd.o ++ ++ccflags-y += -DPRIVATE_MTK_SNAND_HEADER +--- a/drivers/mtd/mtk-snand/mtk-snand-def.h ++++ b/drivers/mtd/mtk-snand/mtk-snand-def.h +@@ -0,0 +1,271 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _MTK_SNAND_DEF_H_ ++#define _MTK_SNAND_DEF_H_ ++ ++#include "mtk-snand-os.h" ++ ++#ifdef PRIVATE_MTK_SNAND_HEADER ++#include "mtk-snand.h" ++#else ++#include ++#endif ++ ++struct mtk_snand_plat_dev; ++ ++enum snand_flash_io { ++ SNAND_IO_1_1_1, ++ SNAND_IO_1_1_2, ++ SNAND_IO_1_2_2, ++ SNAND_IO_1_1_4, ++ SNAND_IO_1_4_4, ++ ++ __SNAND_IO_MAX ++}; ++ ++#define SPI_IO_1_1_1 BIT(SNAND_IO_1_1_1) ++#define SPI_IO_1_1_2 BIT(SNAND_IO_1_1_2) ++#define SPI_IO_1_2_2 BIT(SNAND_IO_1_2_2) ++#define SPI_IO_1_1_4 BIT(SNAND_IO_1_1_4) ++#define SPI_IO_1_4_4 BIT(SNAND_IO_1_4_4) ++ ++struct snand_opcode { ++ uint8_t opcode; ++ uint8_t dummy; ++}; ++ ++struct snand_io_cap { ++ uint8_t caps; ++ struct snand_opcode opcodes[__SNAND_IO_MAX]; ++}; ++ ++#define SNAND_OP(_io, _opcode, _dummy) [_io] = { .opcode = (_opcode), \ ++ .dummy = (_dummy) } ++ ++#define SNAND_IO_CAP(_name, _caps, ...) \ ++ struct snand_io_cap _name = { .caps = (_caps), \ ++ .opcodes = { __VA_ARGS__ } } ++ ++#define SNAND_MAX_ID_LEN 4 ++ ++enum snand_id_type { ++ SNAND_ID_DYMMY, ++ SNAND_ID_ADDR = SNAND_ID_DYMMY, ++ SNAND_ID_DIRECT, ++ ++ __SNAND_ID_TYPE_MAX ++}; ++ ++struct snand_id { ++ uint8_t type; /* enum snand_id_type */ ++ uint8_t len; ++ uint8_t id[SNAND_MAX_ID_LEN]; ++}; ++ ++#define SNAND_ID(_type, ...) \ ++ { .type = (_type), .id = { __VA_ARGS__ }, \ ++ .len = sizeof((uint8_t[]) { __VA_ARGS__ }) } ++ ++struct snand_mem_org { ++ uint16_t pagesize; ++ uint16_t sparesize; ++ uint16_t pages_per_block; ++ uint16_t blocks_per_die; ++ uint16_t planes_per_die; ++ uint16_t ndies; ++}; ++ ++#define SNAND_MEMORG(_ps, _ss, _ppb, _bpd, _ppd, _nd) \ ++ { .pagesize = (_ps), .sparesize = (_ss), .pages_per_block = (_ppb), \ ++ .blocks_per_die = (_bpd), .planes_per_die = (_ppd), .ndies = (_nd) } ++ ++typedef int (*snand_select_die_t)(struct mtk_snand *snf, uint32_t dieidx); ++ ++struct snand_flash_info { ++ const char *model; ++ struct snand_id id; ++ const struct snand_mem_org memorg; ++ const struct snand_io_cap *cap_rd; ++ const struct snand_io_cap *cap_pl; ++ snand_select_die_t select_die; ++}; ++ ++#define SNAND_INFO(_model, _id, _memorg, _cap_rd, _cap_pl, ...) \ ++ { .model = (_model), .id = _id, .memorg = _memorg, \ ++ .cap_rd = (_cap_rd), .cap_pl = (_cap_pl), __VA_ARGS__ } ++ ++const struct snand_flash_info *snand_flash_id_lookup(enum snand_id_type type, ++ const uint8_t *id); ++ ++struct mtk_snand_soc_data { ++ uint16_t sector_size; ++ uint16_t max_sectors; ++ uint16_t fdm_size; ++ uint16_t fdm_ecc_size; ++ uint16_t fifo_size; ++ ++ bool bbm_swap; ++ bool empty_page_check; ++ uint32_t mastersta_mask; ++ ++ const uint8_t *spare_sizes; ++ uint32_t num_spare_size; ++ ++ uint16_t latch_lat; ++ uint16_t sample_delay; ++}; ++ ++enum mtk_ecc_regs { ++ ECC_DECDONE, ++}; ++ ++struct mtk_ecc_soc_data { ++ const uint8_t *ecc_caps; ++ uint32_t num_ecc_cap; ++ const uint32_t *regs; ++ uint16_t mode_shift; ++ uint8_t errnum_bits; ++ uint8_t errnum_shift; ++}; ++ ++struct mtk_snand { ++ struct mtk_snand_plat_dev *pdev; ++ ++ void __iomem *nfi_base; ++ void __iomem *ecc_base; ++ ++ enum mtk_snand_soc soc; ++ const struct mtk_snand_soc_data *nfi_soc; ++ const struct mtk_ecc_soc_data *ecc_soc; ++ bool snfi_quad_spi; ++ bool quad_spi_op; ++ ++ const char *model; ++ uint64_t size; ++ uint64_t die_size; ++ uint32_t erasesize; ++ uint32_t writesize; ++ uint32_t oobsize; ++ ++ uint32_t num_dies; ++ snand_select_die_t select_die; ++ ++ uint8_t opcode_rfc; ++ uint8_t opcode_pl; ++ uint8_t dummy_rfc; ++ uint8_t mode_rfc; ++ uint8_t mode_pl; ++ ++ uint32_t writesize_mask; ++ uint32_t writesize_shift; ++ uint32_t erasesize_mask; ++ uint32_t erasesize_shift; ++ uint64_t die_mask; ++ uint32_t die_shift; ++ ++ uint32_t spare_per_sector; ++ uint32_t raw_sector_size; ++ uint32_t ecc_strength; ++ uint32_t ecc_steps; ++ uint32_t ecc_bytes; ++ uint32_t ecc_parity_bits; ++ ++ uint8_t *page_cache; /* Used by read/write page */ ++ uint8_t *buf_cache; /* Used by block bad/markbad & auto_oob */ ++ int *sect_bf; /* Used by ECC correction */ ++}; ++ ++enum mtk_snand_log_category { ++ SNAND_LOG_NFI, ++ SNAND_LOG_SNFI, ++ SNAND_LOG_ECC, ++ SNAND_LOG_CHIP, ++ ++ __SNAND_LOG_CAT_MAX ++}; ++ ++int mtk_ecc_setup(struct mtk_snand *snf, void *fmdaddr, uint32_t max_ecc_bytes, ++ uint32_t msg_size); ++int mtk_snand_ecc_encoder_start(struct mtk_snand *snf); ++void mtk_snand_ecc_encoder_stop(struct mtk_snand *snf); ++int mtk_snand_ecc_decoder_start(struct mtk_snand *snf); ++void mtk_snand_ecc_decoder_stop(struct mtk_snand *snf); ++int mtk_ecc_wait_decoder_done(struct mtk_snand *snf); ++int mtk_ecc_check_decode_error(struct mtk_snand *snf); ++int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect); ++ ++int mtk_snand_mac_io(struct mtk_snand *snf, const uint8_t *out, uint32_t outlen, ++ uint8_t *in, uint32_t inlen); ++int mtk_snand_set_feature(struct mtk_snand *snf, uint32_t addr, uint32_t val); ++ ++int mtk_snand_log(struct mtk_snand_plat_dev *pdev, ++ enum mtk_snand_log_category cat, const char *fmt, ...); ++ ++#define snand_log_nfi(pdev, fmt, ...) \ ++ mtk_snand_log(pdev, SNAND_LOG_NFI, fmt, ##__VA_ARGS__) ++ ++#define snand_log_snfi(pdev, fmt, ...) \ ++ mtk_snand_log(pdev, SNAND_LOG_SNFI, fmt, ##__VA_ARGS__) ++ ++#define snand_log_ecc(pdev, fmt, ...) \ ++ mtk_snand_log(pdev, SNAND_LOG_ECC, fmt, ##__VA_ARGS__) ++ ++#define snand_log_chip(pdev, fmt, ...) \ ++ mtk_snand_log(pdev, SNAND_LOG_CHIP, fmt, ##__VA_ARGS__) ++ ++/* ffs64 */ ++static inline int mtk_snand_ffs64(uint64_t x) ++{ ++ if (!x) ++ return 0; ++ ++ if (!(x & 0xffffffff)) ++ return ffs((uint32_t)(x >> 32)) + 32; ++ ++ return ffs((uint32_t)(x & 0xffffffff)); ++} ++ ++/* NFI dummy commands */ ++#define NFI_CMD_DUMMY_READ 0x00 ++#define NFI_CMD_DUMMY_WRITE 0x80 ++ ++/* SPI-NAND opcodes */ ++#define SNAND_CMD_RESET 0xff ++#define SNAND_CMD_BLOCK_ERASE 0xd8 ++#define SNAND_CMD_READ_FROM_CACHE_QUAD 0xeb ++#define SNAND_CMD_WINBOND_SELECT_DIE 0xc2 ++#define SNAND_CMD_READ_FROM_CACHE_DUAL 0xbb ++#define SNAND_CMD_READID 0x9f ++#define SNAND_CMD_READ_FROM_CACHE_X4 0x6b ++#define SNAND_CMD_READ_FROM_CACHE_X2 0x3b ++#define SNAND_CMD_PROGRAM_LOAD_X4 0x32 ++#define SNAND_CMD_SET_FEATURE 0x1f ++#define SNAND_CMD_READ_TO_CACHE 0x13 ++#define SNAND_CMD_PROGRAM_EXECUTE 0x10 ++#define SNAND_CMD_GET_FEATURE 0x0f ++#define SNAND_CMD_READ_FROM_CACHE 0x0b ++#define SNAND_CMD_WRITE_ENABLE 0x06 ++#define SNAND_CMD_PROGRAM_LOAD 0x02 ++ ++/* SPI-NAND feature addresses */ ++#define SNAND_FEATURE_MICRON_DIE_ADDR 0xd0 ++#define SNAND_MICRON_DIE_SEL_1 BIT(6) ++ ++#define SNAND_FEATURE_STATUS_ADDR 0xc0 ++#define SNAND_STATUS_OIP BIT(0) ++#define SNAND_STATUS_WEL BIT(1) ++#define SNAND_STATUS_ERASE_FAIL BIT(2) ++#define SNAND_STATUS_PROGRAM_FAIL BIT(3) ++ ++#define SNAND_FEATURE_CONFIG_ADDR 0xb0 ++#define SNAND_FEATURE_QUAD_ENABLE BIT(0) ++#define SNAND_FEATURE_ECC_EN BIT(4) ++ ++#define SNAND_FEATURE_PROTECT_ADDR 0xa0 ++ ++#endif /* _MTK_SNAND_DEF_H_ */ +--- a/drivers/mtd/mtk-snand/mtk-snand-ecc.c ++++ b/drivers/mtd/mtk-snand/mtk-snand-ecc.c +@@ -0,0 +1,411 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include "mtk-snand-def.h" ++ ++/* ECC registers */ ++#define ECC_ENCCON 0x000 ++#define ENC_EN BIT(0) ++ ++#define ECC_ENCCNFG 0x004 ++#define ENC_MS_S 16 ++#define ENC_BURST_EN BIT(8) ++#define ENC_TNUM_S 0 ++ ++#define ECC_ENCIDLE 0x00c ++#define ENC_IDLE BIT(0) ++ ++#define ECC_DECCON 0x100 ++#define DEC_EN BIT(0) ++ ++#define ECC_DECCNFG 0x104 ++#define DEC_EMPTY_EN BIT(31) ++#define DEC_CS_S 16 ++#define DEC_CON_S 12 ++#define DEC_CON_CORRECT 3 ++#define DEC_BURST_EN BIT(8) ++#define DEC_TNUM_S 0 ++ ++#define ECC_DECIDLE 0x10c ++#define DEC_IDLE BIT(0) ++ ++#define ECC_DECENUM0 0x114 ++#define ECC_DECENUM(n) (ECC_DECENUM0 + (n) * 4) ++ ++/* ECC_ENCIDLE & ECC_DECIDLE */ ++#define ECC_IDLE BIT(0) ++ ++/* ENC_MODE & DEC_MODE */ ++#define ECC_MODE_NFI 1 ++ ++#define ECC_TIMEOUT 500000 ++ ++static const uint8_t mt7622_ecc_caps[] = { 4, 6, 8, 10, 12 }; ++ ++static const uint8_t mt7981_ecc_caps[] = { ++ 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 ++}; ++ ++static const uint8_t mt7986_ecc_caps[] = { ++ 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 ++}; ++ ++static const uint32_t mt7622_ecc_regs[] = { ++ [ECC_DECDONE] = 0x11c, ++}; ++ ++static const uint32_t mt7981_ecc_regs[] = { ++ [ECC_DECDONE] = 0x124, ++}; ++ ++static const uint32_t mt7986_ecc_regs[] = { ++ [ECC_DECDONE] = 0x124, ++}; ++ ++static const struct mtk_ecc_soc_data mtk_ecc_socs[__SNAND_SOC_MAX] = { ++ [SNAND_SOC_MT7622] = { ++ .ecc_caps = mt7622_ecc_caps, ++ .num_ecc_cap = ARRAY_SIZE(mt7622_ecc_caps), ++ .regs = mt7622_ecc_regs, ++ .mode_shift = 4, ++ .errnum_bits = 5, ++ .errnum_shift = 5, ++ }, ++ [SNAND_SOC_MT7629] = { ++ .ecc_caps = mt7622_ecc_caps, ++ .num_ecc_cap = ARRAY_SIZE(mt7622_ecc_caps), ++ .regs = mt7622_ecc_regs, ++ .mode_shift = 4, ++ .errnum_bits = 5, ++ .errnum_shift = 5, ++ }, ++ [SNAND_SOC_MT7981] = { ++ .ecc_caps = mt7981_ecc_caps, ++ .num_ecc_cap = ARRAY_SIZE(mt7981_ecc_caps), ++ .regs = mt7981_ecc_regs, ++ .mode_shift = 5, ++ .errnum_bits = 5, ++ .errnum_shift = 8, ++ }, ++ [SNAND_SOC_MT7986] = { ++ .ecc_caps = mt7986_ecc_caps, ++ .num_ecc_cap = ARRAY_SIZE(mt7986_ecc_caps), ++ .regs = mt7986_ecc_regs, ++ .mode_shift = 5, ++ .errnum_bits = 5, ++ .errnum_shift = 8, ++ }, ++}; ++ ++static inline uint32_t ecc_read32(struct mtk_snand *snf, uint32_t reg) ++{ ++ return readl(snf->ecc_base + reg); ++} ++ ++static inline void ecc_write32(struct mtk_snand *snf, uint32_t reg, ++ uint32_t val) ++{ ++ writel(val, snf->ecc_base + reg); ++} ++ ++static inline void ecc_write16(struct mtk_snand *snf, uint32_t reg, ++ uint16_t val) ++{ ++ writew(val, snf->ecc_base + reg); ++} ++ ++static int mtk_ecc_poll(struct mtk_snand *snf, uint32_t reg, uint32_t bits) ++{ ++ uint32_t val; ++ ++ return read16_poll_timeout(snf->ecc_base + reg, val, (val & bits), 0, ++ ECC_TIMEOUT); ++} ++ ++static int mtk_ecc_wait_idle(struct mtk_snand *snf, uint32_t reg) ++{ ++ int ret; ++ ++ ret = mtk_ecc_poll(snf, reg, ECC_IDLE); ++ if (ret) { ++ snand_log_ecc(snf->pdev, "ECC engine is busy\n"); ++ return -EBUSY; ++ } ++ ++ return 0; ++} ++ ++int mtk_ecc_setup(struct mtk_snand *snf, void *fmdaddr, uint32_t max_ecc_bytes, ++ uint32_t msg_size) ++{ ++ uint32_t i, val, ecc_msg_bits, ecc_strength; ++ int ret; ++ ++ snf->ecc_soc = &mtk_ecc_socs[snf->soc]; ++ ++ snf->ecc_parity_bits = fls(1 + 8 * msg_size); ++ ecc_strength = max_ecc_bytes * 8 / snf->ecc_parity_bits; ++ ++ for (i = snf->ecc_soc->num_ecc_cap - 1; i >= 0; i--) { ++ if (snf->ecc_soc->ecc_caps[i] <= ecc_strength) ++ break; ++ } ++ ++ if (unlikely(i < 0)) { ++ snand_log_ecc(snf->pdev, "Page size %u+%u is not supported\n", ++ snf->writesize, snf->oobsize); ++ return -ENOTSUPP; ++ } ++ ++ snf->ecc_strength = snf->ecc_soc->ecc_caps[i]; ++ snf->ecc_bytes = DIV_ROUND_UP(snf->ecc_strength * snf->ecc_parity_bits, ++ 8); ++ ++ /* Encoder config */ ++ ecc_write16(snf, ECC_ENCCON, 0); ++ ret = mtk_ecc_wait_idle(snf, ECC_ENCIDLE); ++ if (ret) ++ return ret; ++ ++ ecc_msg_bits = msg_size * 8; ++ val = (ecc_msg_bits << ENC_MS_S) | ++ (ECC_MODE_NFI << snf->ecc_soc->mode_shift) | i; ++ ecc_write32(snf, ECC_ENCCNFG, val); ++ ++ /* Decoder config */ ++ ecc_write16(snf, ECC_DECCON, 0); ++ ret = mtk_ecc_wait_idle(snf, ECC_DECIDLE); ++ if (ret) ++ return ret; ++ ++ ecc_msg_bits += snf->ecc_strength * snf->ecc_parity_bits; ++ val = DEC_EMPTY_EN | (ecc_msg_bits << DEC_CS_S) | ++ (DEC_CON_CORRECT << DEC_CON_S) | ++ (ECC_MODE_NFI << snf->ecc_soc->mode_shift) | i; ++ ecc_write32(snf, ECC_DECCNFG, val); ++ ++ return 0; ++} ++ ++int mtk_snand_ecc_encoder_start(struct mtk_snand *snf) ++{ ++ int ret; ++ ++ ret = mtk_ecc_wait_idle(snf, ECC_ENCIDLE); ++ if (ret) { ++ ecc_write16(snf, ECC_ENCCON, 0); ++ mtk_ecc_wait_idle(snf, ECC_ENCIDLE); ++ } ++ ++ ecc_write16(snf, ECC_ENCCON, ENC_EN); ++ ++ return 0; ++} ++ ++void mtk_snand_ecc_encoder_stop(struct mtk_snand *snf) ++{ ++ mtk_ecc_wait_idle(snf, ECC_ENCIDLE); ++ ecc_write16(snf, ECC_ENCCON, 0); ++} ++ ++int mtk_snand_ecc_decoder_start(struct mtk_snand *snf) ++{ ++ int ret; ++ ++ ret = mtk_ecc_wait_idle(snf, ECC_DECIDLE); ++ if (ret) { ++ ecc_write16(snf, ECC_DECCON, 0); ++ mtk_ecc_wait_idle(snf, ECC_DECIDLE); ++ } ++ ++ ecc_write16(snf, ECC_DECCON, DEC_EN); ++ ++ return 0; ++} ++ ++void mtk_snand_ecc_decoder_stop(struct mtk_snand *snf) ++{ ++ mtk_ecc_wait_idle(snf, ECC_DECIDLE); ++ ecc_write16(snf, ECC_DECCON, 0); ++} ++ ++int mtk_ecc_wait_decoder_done(struct mtk_snand *snf) ++{ ++ uint16_t val, step_mask = (1 << snf->ecc_steps) - 1; ++ uint32_t reg = snf->ecc_soc->regs[ECC_DECDONE]; ++ int ret; ++ ++ ret = read16_poll_timeout(snf->ecc_base + reg, val, ++ (val & step_mask) == step_mask, 0, ++ ECC_TIMEOUT); ++ if (ret) ++ snand_log_ecc(snf->pdev, "ECC decoder is busy\n"); ++ ++ return ret; ++} ++ ++int mtk_ecc_check_decode_error(struct mtk_snand *snf) ++{ ++ uint32_t i, regi, fi, errnum; ++ uint32_t errnum_shift = snf->ecc_soc->errnum_shift; ++ uint32_t errnum_mask = (1 << snf->ecc_soc->errnum_bits) - 1; ++ int ret = 0; ++ ++ for (i = 0; i < snf->ecc_steps; i++) { ++ regi = i / 4; ++ fi = i % 4; ++ ++ errnum = ecc_read32(snf, ECC_DECENUM(regi)); ++ errnum = (errnum >> (fi * errnum_shift)) & errnum_mask; ++ ++ if (errnum <= snf->ecc_strength) { ++ snf->sect_bf[i] = errnum; ++ } else { ++ snf->sect_bf[i] = -1; ++ ret = -EBADMSG; ++ } ++ } ++ ++ return ret; ++} ++ ++static int mtk_ecc_check_buf_bitflips(struct mtk_snand *snf, const void *buf, ++ size_t len, uint32_t bitflips) ++{ ++ const uint8_t *buf8 = buf; ++ const uint32_t *buf32; ++ uint32_t d, weight; ++ ++ while (len && ((uintptr_t)buf8) % sizeof(uint32_t)) { ++ weight = hweight8(*buf8); ++ bitflips += BITS_PER_BYTE - weight; ++ buf8++; ++ len--; ++ ++ if (bitflips > snf->ecc_strength) ++ return -EBADMSG; ++ } ++ ++ buf32 = (const uint32_t *)buf8; ++ while (len >= sizeof(uint32_t)) { ++ d = *buf32; ++ ++ if (d != ~0) { ++ weight = hweight32(d); ++ bitflips += sizeof(uint32_t) * BITS_PER_BYTE - weight; ++ } ++ ++ buf32++; ++ len -= sizeof(uint32_t); ++ ++ if (bitflips > snf->ecc_strength) ++ return -EBADMSG; ++ } ++ ++ buf8 = (const uint8_t *)buf32; ++ while (len) { ++ weight = hweight8(*buf8); ++ bitflips += BITS_PER_BYTE - weight; ++ buf8++; ++ len--; ++ ++ if (bitflips > snf->ecc_strength) ++ return -EBADMSG; ++ } ++ ++ return bitflips; ++} ++ ++static int mtk_ecc_check_parity_bitflips(struct mtk_snand *snf, const void *buf, ++ uint32_t bits, uint32_t bitflips) ++{ ++ uint32_t len, i; ++ uint8_t b; ++ int rc; ++ ++ len = bits >> 3; ++ bits &= 7; ++ ++ rc = mtk_ecc_check_buf_bitflips(snf, buf, len, bitflips); ++ if (!bits || rc < 0) ++ return rc; ++ ++ bitflips = rc; ++ ++ /* We want a precise count of bits */ ++ b = ((const uint8_t *)buf)[len]; ++ for (i = 0; i < bits; i++) { ++ if (!(b & BIT(i))) ++ bitflips++; ++ } ++ ++ if (bitflips > snf->ecc_strength) ++ return -EBADMSG; ++ ++ return bitflips; ++} ++ ++static void mtk_ecc_reset_parity(void *buf, uint32_t bits) ++{ ++ uint32_t len; ++ ++ len = bits >> 3; ++ bits &= 7; ++ ++ memset(buf, 0xff, len); ++ ++ /* Only reset bits protected by ECC to 1 */ ++ if (bits) ++ ((uint8_t *)buf)[len] |= GENMASK(bits - 1, 0); ++} ++ ++int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect) ++{ ++ uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size; ++ uint8_t *oob = snf->page_cache + snf->writesize; ++ uint8_t *data_ptr, *fdm_ptr, *ecc_ptr; ++ int bitflips = 0, ecc_bits, parity_bits; ++ ++ parity_bits = fls(snf->nfi_soc->sector_size * 8); ++ ecc_bits = snf->ecc_strength * parity_bits; ++ ++ data_ptr = snf->page_cache + sect * snf->nfi_soc->sector_size; ++ fdm_ptr = oob + sect * snf->nfi_soc->fdm_size; ++ ecc_ptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size + ++ sect * ecc_bytes; ++ ++ /* ++ * Check whether DATA + FDM + ECC of a sector contains correctable ++ * bitflips ++ */ ++ bitflips = mtk_ecc_check_buf_bitflips(snf, data_ptr, ++ snf->nfi_soc->sector_size, ++ bitflips); ++ if (bitflips < 0) ++ return -EBADMSG; ++ ++ bitflips = mtk_ecc_check_buf_bitflips(snf, fdm_ptr, ++ snf->nfi_soc->fdm_ecc_size, ++ bitflips); ++ if (bitflips < 0) ++ return -EBADMSG; ++ ++ bitflips = mtk_ecc_check_parity_bitflips(snf, ecc_ptr, ecc_bits, ++ bitflips); ++ if (bitflips < 0) ++ return -EBADMSG; ++ ++ if (!bitflips) ++ return 0; ++ ++ /* Reset the data of this sector to 0xff */ ++ memset(data_ptr, 0xff, snf->nfi_soc->sector_size); ++ memset(fdm_ptr, 0xff, snf->nfi_soc->fdm_ecc_size); ++ mtk_ecc_reset_parity(ecc_ptr, ecc_bits); ++ ++ return bitflips; ++} +--- a/drivers/mtd/mtk-snand/mtk-snand-ids.c ++++ b/drivers/mtd/mtk-snand/mtk-snand-ids.c +@@ -0,0 +1,519 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include "mtk-snand-def.h" ++ ++static int mtk_snand_winbond_select_die(struct mtk_snand *snf, uint32_t dieidx); ++static int mtk_snand_micron_select_die(struct mtk_snand *snf, uint32_t dieidx); ++ ++#define SNAND_MEMORG_512M_2K_64 SNAND_MEMORG(2048, 64, 64, 512, 1, 1) ++#define SNAND_MEMORG_1G_2K_64 SNAND_MEMORG(2048, 64, 64, 1024, 1, 1) ++#define SNAND_MEMORG_2G_2K_64 SNAND_MEMORG(2048, 64, 64, 2048, 1, 1) ++#define SNAND_MEMORG_2G_2K_120 SNAND_MEMORG(2048, 120, 64, 2048, 1, 1) ++#define SNAND_MEMORG_4G_2K_64 SNAND_MEMORG(2048, 64, 64, 4096, 1, 1) ++#define SNAND_MEMORG_1G_2K_120 SNAND_MEMORG(2048, 120, 64, 1024, 1, 1) ++#define SNAND_MEMORG_1G_2K_128 SNAND_MEMORG(2048, 128, 64, 1024, 1, 1) ++#define SNAND_MEMORG_2G_2K_128 SNAND_MEMORG(2048, 128, 64, 2048, 1, 1) ++#define SNAND_MEMORG_4G_2K_128 SNAND_MEMORG(2048, 128, 64, 4096, 1, 1) ++#define SNAND_MEMORG_4G_4K_240 SNAND_MEMORG(4096, 240, 64, 2048, 1, 1) ++#define SNAND_MEMORG_4G_4K_256 SNAND_MEMORG(4096, 256, 64, 2048, 1, 1) ++#define SNAND_MEMORG_8G_4K_256 SNAND_MEMORG(4096, 256, 64, 4096, 1, 1) ++#define SNAND_MEMORG_2G_2K_64_2P SNAND_MEMORG(2048, 64, 64, 2048, 2, 1) ++#define SNAND_MEMORG_2G_2K_64_2D SNAND_MEMORG(2048, 64, 64, 1024, 1, 2) ++#define SNAND_MEMORG_2G_2K_128_2P SNAND_MEMORG(2048, 128, 64, 2048, 2, 1) ++#define SNAND_MEMORG_4G_2K_64_2P SNAND_MEMORG(2048, 64, 64, 4096, 2, 1) ++#define SNAND_MEMORG_4G_2K_128_2P_2D SNAND_MEMORG(2048, 128, 64, 2048, 2, 2) ++#define SNAND_MEMORG_8G_4K_256_2D SNAND_MEMORG(4096, 256, 64, 2048, 1, 2) ++ ++static const SNAND_IO_CAP(snand_cap_read_from_cache_quad, ++ SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2 | SPI_IO_1_1_4 | ++ SPI_IO_1_4_4, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8), ++ SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8), ++ SNAND_OP(SNAND_IO_1_2_2, SNAND_CMD_READ_FROM_CACHE_DUAL, 4), ++ SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8), ++ SNAND_OP(SNAND_IO_1_4_4, SNAND_CMD_READ_FROM_CACHE_QUAD, 4)); ++ ++static const SNAND_IO_CAP(snand_cap_read_from_cache_quad_q2d, ++ SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2 | SPI_IO_1_1_4 | ++ SPI_IO_1_4_4, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8), ++ SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8), ++ SNAND_OP(SNAND_IO_1_2_2, SNAND_CMD_READ_FROM_CACHE_DUAL, 4), ++ SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8), ++ SNAND_OP(SNAND_IO_1_4_4, SNAND_CMD_READ_FROM_CACHE_QUAD, 2)); ++ ++static const SNAND_IO_CAP(snand_cap_read_from_cache_quad_a8d, ++ SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2 | SPI_IO_1_1_4 | ++ SPI_IO_1_4_4, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8), ++ SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8), ++ SNAND_OP(SNAND_IO_1_2_2, SNAND_CMD_READ_FROM_CACHE_DUAL, 8), ++ SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8), ++ SNAND_OP(SNAND_IO_1_4_4, SNAND_CMD_READ_FROM_CACHE_QUAD, 8)); ++ ++static const SNAND_IO_CAP(snand_cap_read_from_cache_x4, ++ SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_1_4, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8), ++ SNAND_OP(SNAND_IO_1_1_2, SNAND_CMD_READ_FROM_CACHE_X2, 8), ++ SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8)); ++ ++static const SNAND_IO_CAP(snand_cap_read_from_cache_x4_only, ++ SPI_IO_1_1_1 | SPI_IO_1_1_4, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_READ_FROM_CACHE, 8), ++ SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_READ_FROM_CACHE_X4, 8)); ++ ++static const SNAND_IO_CAP(snand_cap_program_load_x1, ++ SPI_IO_1_1_1, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_PROGRAM_LOAD, 0)); ++ ++static const SNAND_IO_CAP(snand_cap_program_load_x4, ++ SPI_IO_1_1_1 | SPI_IO_1_1_4, ++ SNAND_OP(SNAND_IO_1_1_1, SNAND_CMD_PROGRAM_LOAD, 0), ++ SNAND_OP(SNAND_IO_1_1_4, SNAND_CMD_PROGRAM_LOAD_X4, 0)); ++ ++static const struct snand_flash_info snand_flash_ids[] = { ++ SNAND_INFO("W25N512GV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xaa, 0x20), ++ SNAND_MEMORG_512M_2K_64, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("W25N01GV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xaa, 0x21), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("W25M02GV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xab, 0x21), ++ SNAND_MEMORG_2G_2K_64_2D, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4, ++ mtk_snand_winbond_select_die), ++ SNAND_INFO("W25N01KV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xae, 0x21), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("W25N02KV", SNAND_ID(SNAND_ID_DYMMY, 0xef, 0xaa, 0x22), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("GD5F1GQ4UAWxx", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0x10), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F1GQ4UExIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd1), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F1GQ4UExxH", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd9), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F1GQ4xAYIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xf1), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F1GQ5UExxG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0x51), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F2GQ4UExIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd2), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F2GQ5UExxH", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0x32), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_a8d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F2GQ4xAYIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xf2), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F4GQ4UBxIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xd4), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F4GQ4xAYIG", SNAND_ID(SNAND_ID_ADDR, 0xc8, 0xf4), ++ SNAND_MEMORG_4G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F2GQ5UExxG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x52), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_a8d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("GD5F4GQ4UCxIG", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0xb4), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("MX35LF1GE4AB", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x12), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF1G24AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x14), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX31LF1GE4BC", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x1e), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF2GE4AB", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x22), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF2G24AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x24), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF2GE4AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x26), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF2G14AC", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x20), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF4G24AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x35), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MX35LF4GE4AD", SNAND_ID(SNAND_ID_DYMMY, 0xc2, 0x37), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("MT29F1G01AAADD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x12), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x1), ++ SNAND_INFO("MT29F1G01ABAFD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x14), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MT29F2G01AAAED", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x9f), ++ SNAND_MEMORG_2G_2K_64_2P, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x1), ++ SNAND_INFO("MT29F2G01ABAGD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x24), ++ SNAND_MEMORG_2G_2K_128_2P, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MT29F4G01AAADD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x32), ++ SNAND_MEMORG_4G_2K_64_2P, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x1), ++ SNAND_INFO("MT29F4G01ABAFD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x34), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("MT29F4G01ADAGD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x36), ++ SNAND_MEMORG_4G_2K_128_2P_2D, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4, ++ mtk_snand_micron_select_die), ++ SNAND_INFO("MT29F8G01ADAFD", SNAND_ID(SNAND_ID_DYMMY, 0x2c, 0x46), ++ SNAND_MEMORG_8G_4K_256_2D, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4, ++ mtk_snand_micron_select_die), ++ ++ SNAND_INFO("TC58CVG0S3HRAIG", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xc2), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x1), ++ SNAND_INFO("TC58CVG1S3HRAIG", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xcb), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x1), ++ SNAND_INFO("TC58CVG2S0HRAIG", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xcd), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x1), ++ SNAND_INFO("TC58CVG0S3HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xe2), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("TC58CVG1S3HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xeb), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("TC58CVG2S0HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xed), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("TH58CVG3S0HRAIJ", SNAND_ID(SNAND_ID_DYMMY, 0x98, 0xe4), ++ SNAND_MEMORG_8G_4K_256, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("F50L512M41A", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x20), ++ SNAND_MEMORG_512M_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("F50L1G41A", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x21), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("F50L1G41LB", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x01), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("F50L2G41LB", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x0a), ++ SNAND_MEMORG_2G_2K_64_2D, ++ &snand_cap_read_from_cache_quad, ++ &snand_cap_program_load_x4, ++ mtk_snand_winbond_select_die), ++ ++ SNAND_INFO("CS11G0T0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x00), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("CS11G0G0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x10), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("CS11G0S0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x20), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("CS11G1T0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x01), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("CS11G1S0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x21), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("CS11G2T0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x02), ++ SNAND_MEMORG_4G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("CS11G2S0A0AA", SNAND_ID(SNAND_ID_DYMMY, 0x6b, 0x22), ++ SNAND_MEMORG_4G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("EM73B044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x01), ++ SNAND_MEMORG_512M_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044SNB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x11), ++ SNAND_MEMORG_1G_2K_120, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044SNF", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x09), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x18), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x19), ++ SNAND_MEMORG(2048, 64, 128, 512, 1, 1), ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044VCD", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1c), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1d), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1e), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044VCC", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x22), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044VCF", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x25), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044SNC", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x31), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044SNC", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0a), ++ SNAND_MEMORG_2G_2K_120, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x12), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044SNF", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x10), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x13), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x14), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCD", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x17), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCH", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1b), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1d), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCG", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x1f), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCE", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x20), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCL", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2e), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044SNB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x32), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73E044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x03), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73E044SND", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0b), ++ SNAND_MEMORG_4G_4K_240, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73E044SNB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x23), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73E044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2c), ++ SNAND_MEMORG_4G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73E044VCB", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2f), ++ SNAND_MEMORG_4G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73F044SNA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x24), ++ SNAND_MEMORG_8G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73F044VCA", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x2d), ++ SNAND_MEMORG_8G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73E044SNE", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0e), ++ SNAND_MEMORG_8G_4K_256, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73C044SNG", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0c), ++ SNAND_MEMORG_1G_2K_120, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("EM73D044VCN", SNAND_ID(SNAND_ID_DYMMY, 0xd5, 0x0f), ++ SNAND_MEMORG_2G_2K_64, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("FM35Q1GA", SNAND_ID(SNAND_ID_DYMMY, 0xe5, 0x71), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4_only, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("PN26G01A", SNAND_ID(SNAND_ID_DYMMY, 0xa1, 0xe1), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("PN26G02A", SNAND_ID(SNAND_ID_DYMMY, 0xa1, 0xe2), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("IS37SML01G1", SNAND_ID(SNAND_ID_DYMMY, 0xc8, 0x21), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("ATO25D1GA", SNAND_ID(SNAND_ID_DYMMY, 0x9b, 0x12), ++ SNAND_MEMORG_1G_2K_64, ++ &snand_cap_read_from_cache_x4_only, ++ &snand_cap_program_load_x4), ++ ++ SNAND_INFO("HYF1GQ4U", SNAND_ID(SNAND_ID_DYMMY, 0xc9, 0x51), ++ SNAND_MEMORG_1G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++ SNAND_INFO("HYF2GQ4U", SNAND_ID(SNAND_ID_DYMMY, 0xc9, 0x52), ++ SNAND_MEMORG_2G_2K_128, ++ &snand_cap_read_from_cache_quad_q2d, ++ &snand_cap_program_load_x4), ++}; ++ ++static int mtk_snand_winbond_select_die(struct mtk_snand *snf, uint32_t dieidx) ++{ ++ uint8_t op[2]; ++ ++ if (dieidx > 1) { ++ snand_log_chip(snf->pdev, "Invalid die index %u\n", dieidx); ++ return -EINVAL; ++ } ++ ++ op[0] = SNAND_CMD_WINBOND_SELECT_DIE; ++ op[1] = (uint8_t)dieidx; ++ ++ return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0); ++} ++ ++static int mtk_snand_micron_select_die(struct mtk_snand *snf, uint32_t dieidx) ++{ ++ int ret; ++ ++ if (dieidx > 1) { ++ snand_log_chip(snf->pdev, "Invalid die index %u\n", dieidx); ++ return -EINVAL; ++ } ++ ++ ret = mtk_snand_set_feature(snf, SNAND_FEATURE_MICRON_DIE_ADDR, ++ SNAND_MICRON_DIE_SEL_1); ++ if (ret) { ++ snand_log_chip(snf->pdev, ++ "Failed to set die selection feature\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++const struct snand_flash_info *snand_flash_id_lookup(enum snand_id_type type, ++ const uint8_t *id) ++{ ++ const struct snand_id *fid; ++ uint32_t i; ++ ++ for (i = 0; i < ARRAY_SIZE(snand_flash_ids); i++) { ++ if (snand_flash_ids[i].id.type != type) ++ continue; ++ ++ fid = &snand_flash_ids[i].id; ++ if (memcmp(fid->id, id, fid->len)) ++ continue; ++ ++ return &snand_flash_ids[i]; ++ } ++ ++ return NULL; ++} +--- a/drivers/mtd/mtk-snand/mtk-snand-mtd.c ++++ b/drivers/mtd/mtk-snand/mtk-snand-mtd.c +@@ -0,0 +1,535 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mtk-snand.h" ++ ++struct mtk_snand_mtd { ++ struct udevice *dev; ++ struct mtk_snand *snf; ++ struct mtk_snand_chip_info cinfo; ++ uint8_t *page_cache; ++}; ++ ++static const char snand_mtd_name_prefix[] = "spi-nand"; ++ ++static u32 snandidx; ++ ++static inline struct mtk_snand_mtd *mtd_to_msm(struct mtd_info *mtd) ++{ ++ return mtd->priv; ++} ++ ++static int mtk_snand_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ u64 start_addr, end_addr; ++ int ret; ++ ++ /* Do not allow write past end of device */ ++ if ((instr->addr + instr->len) > mtd->size) { ++ pr_debug("%s: attempt to erase beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ start_addr = instr->addr & (~mtd->erasesize_mask); ++ end_addr = instr->addr + instr->len; ++ if (end_addr & mtd->erasesize_mask) { ++ end_addr = (end_addr + mtd->erasesize_mask) & ++ (~mtd->erasesize_mask); ++ } ++ ++ instr->state = MTD_ERASING; ++ ++ while (start_addr < end_addr) { ++ schedule(); ++ ++ if (mtk_snand_block_isbad(msm->snf, start_addr)) { ++ if (!instr->scrub) { ++ instr->fail_addr = start_addr; ++ ret = -EIO; ++ break; ++ } ++ } ++ ++ ret = mtk_snand_erase_block(msm->snf, start_addr); ++ if (ret) { ++ instr->fail_addr = start_addr; ++ break; ++ } ++ ++ start_addr += mtd->erasesize; ++ } ++ ++ if (!ret) { ++ instr->state = MTD_ERASE_DONE; ++ } else { ++ instr->state = MTD_ERASE_FAILED; ++ ret = -EIO; ++ } ++ ++ return ret; ++} ++ ++static int mtk_snand_mtd_read_data(struct mtk_snand_mtd *msm, uint64_t addr, ++ struct mtd_oob_ops *ops) ++{ ++ struct mtd_info *mtd = dev_get_uclass_priv(msm->dev); ++ size_t len, ooblen, maxooblen, chklen; ++ uint32_t col, ooboffs; ++ uint8_t *datcache, *oobcache; ++ bool ecc_failed = false, raw = ops->mode == MTD_OPS_RAW ? true : false; ++ int ret, max_bitflips = 0; ++ ++ col = addr & mtd->writesize_mask; ++ addr &= ~mtd->writesize_mask; ++ maxooblen = mtd_oobavail(mtd, ops); ++ ooboffs = ops->ooboffs; ++ ooblen = ops->ooblen; ++ len = ops->len; ++ ++ datcache = len ? msm->page_cache : NULL; ++ oobcache = ooblen ? msm->page_cache + mtd->writesize : NULL; ++ ++ ops->oobretlen = 0; ++ ops->retlen = 0; ++ ++ while (len || ooblen) { ++ schedule(); ++ ++ if (ops->mode == MTD_OPS_AUTO_OOB) ++ ret = mtk_snand_read_page_auto_oob(msm->snf, addr, ++ datcache, oobcache, maxooblen, NULL, raw); ++ else ++ ret = mtk_snand_read_page(msm->snf, addr, datcache, ++ oobcache, raw); ++ ++ if (ret < 0 && ret != -EBADMSG) ++ return ret; ++ ++ if (ret == -EBADMSG) { ++ mtd->ecc_stats.failed++; ++ ecc_failed = true; ++ } else { ++ mtd->ecc_stats.corrected += ret; ++ max_bitflips = max_t(int, ret, max_bitflips); ++ } ++ ++ mtd->ecc_stats.corrected += ret; ++ max_bitflips = max_t(int, ret, max_bitflips); ++ ++ if (len) { ++ /* Move data */ ++ chklen = mtd->writesize - col; ++ if (chklen > len) ++ chklen = len; ++ ++ memcpy(ops->datbuf + ops->retlen, datcache + col, ++ chklen); ++ len -= chklen; ++ col = 0; /* (col + chklen) % */ ++ ops->retlen += chklen; ++ } ++ ++ if (ooblen) { ++ /* Move oob */ ++ chklen = maxooblen - ooboffs; ++ if (chklen > ooblen) ++ chklen = ooblen; ++ ++ memcpy(ops->oobbuf + ops->oobretlen, oobcache + ooboffs, ++ chklen); ++ ooblen -= chklen; ++ ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ ++ ops->oobretlen += chklen; ++ } ++ ++ addr += mtd->writesize; ++ } ++ ++ return ecc_failed ? -EBADMSG : max_bitflips; ++} ++ ++static int mtk_snand_mtd_read_oob(struct mtd_info *mtd, loff_t from, ++ struct mtd_oob_ops *ops) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ uint32_t maxooblen; ++ ++ if (!ops->oobbuf && !ops->datbuf) { ++ if (ops->ooblen || ops->len) ++ return -EINVAL; ++ ++ return 0; ++ } ++ ++ switch (ops->mode) { ++ case MTD_OPS_PLACE_OOB: ++ case MTD_OPS_AUTO_OOB: ++ case MTD_OPS_RAW: ++ break; ++ default: ++ pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode); ++ return -EINVAL; ++ } ++ ++ maxooblen = mtd_oobavail(mtd, ops); ++ ++ /* Do not allow read past end of device */ ++ if (ops->datbuf && (from + ops->len) > mtd->size) { ++ pr_debug("%s: attempt to read beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (unlikely(ops->ooboffs >= maxooblen)) { ++ pr_debug("%s: attempt to start read outside oob\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (unlikely(from >= mtd->size || ++ ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - ++ (from >> mtd->writesize_shift)) * maxooblen)) { ++ pr_debug("%s: attempt to read beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ return mtk_snand_mtd_read_data(msm, from, ops); ++} ++ ++static int mtk_snand_mtd_write_data(struct mtk_snand_mtd *msm, uint64_t addr, ++ struct mtd_oob_ops *ops) ++{ ++ struct mtd_info *mtd = dev_get_uclass_priv(msm->dev); ++ size_t len, ooblen, maxooblen, chklen, oobwrlen; ++ uint32_t col, ooboffs; ++ uint8_t *datcache, *oobcache; ++ bool raw = ops->mode == MTD_OPS_RAW ? true : false; ++ int ret; ++ ++ col = addr & mtd->writesize_mask; ++ addr &= ~mtd->writesize_mask; ++ maxooblen = mtd_oobavail(mtd, ops); ++ ooboffs = ops->ooboffs; ++ ooblen = ops->ooblen; ++ len = ops->len; ++ ++ datcache = len ? msm->page_cache : NULL; ++ oobcache = ooblen ? msm->page_cache + mtd->writesize : NULL; ++ ++ ops->oobretlen = 0; ++ ops->retlen = 0; ++ ++ while (len || ooblen) { ++ schedule(); ++ ++ if (len) { ++ /* Move data */ ++ chklen = mtd->writesize - col; ++ if (chklen > len) ++ chklen = len; ++ ++ memset(datcache, 0xff, col); ++ memcpy(datcache + col, ops->datbuf + ops->retlen, ++ chklen); ++ memset(datcache + col + chklen, 0xff, ++ mtd->writesize - col - chklen); ++ len -= chklen; ++ col = 0; /* (col + chklen) % */ ++ ops->retlen += chklen; ++ } ++ ++ oobwrlen = 0; ++ if (ooblen) { ++ /* Move oob */ ++ chklen = maxooblen - ooboffs; ++ if (chklen > ooblen) ++ chklen = ooblen; ++ ++ memset(oobcache, 0xff, ooboffs); ++ memcpy(oobcache + ooboffs, ++ ops->oobbuf + ops->oobretlen, chklen); ++ memset(oobcache + ooboffs + chklen, 0xff, ++ mtd->oobsize - ooboffs - chklen); ++ oobwrlen = chklen + ooboffs; ++ ooblen -= chklen; ++ ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ ++ ops->oobretlen += chklen; ++ } ++ ++ if (ops->mode == MTD_OPS_AUTO_OOB) ++ ret = mtk_snand_write_page_auto_oob(msm->snf, addr, ++ datcache, oobcache, oobwrlen, NULL, raw); ++ else ++ ret = mtk_snand_write_page(msm->snf, addr, datcache, ++ oobcache, raw); ++ ++ if (ret) ++ return ret; ++ ++ addr += mtd->writesize; ++ } ++ ++ return 0; ++} ++ ++static int mtk_snand_mtd_write_oob(struct mtd_info *mtd, loff_t to, ++ struct mtd_oob_ops *ops) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ uint32_t maxooblen; ++ ++ if (!ops->oobbuf && !ops->datbuf) { ++ if (ops->ooblen || ops->len) ++ return -EINVAL; ++ ++ return 0; ++ } ++ ++ switch (ops->mode) { ++ case MTD_OPS_PLACE_OOB: ++ case MTD_OPS_AUTO_OOB: ++ case MTD_OPS_RAW: ++ break; ++ default: ++ pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode); ++ return -EINVAL; ++ } ++ ++ maxooblen = mtd_oobavail(mtd, ops); ++ ++ /* Do not allow write past end of device */ ++ if (ops->datbuf && (to + ops->len) > mtd->size) { ++ pr_debug("%s: attempt to write beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (unlikely(ops->ooboffs >= maxooblen)) { ++ pr_debug("%s: attempt to start write outside oob\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (unlikely(to >= mtd->size || ++ ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - ++ (to >> mtd->writesize_shift)) * maxooblen)) { ++ pr_debug("%s: attempt to write beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ return mtk_snand_mtd_write_data(msm, to, ops); ++} ++ ++static int mtk_snand_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, ++ size_t *retlen, u_char *buf) ++{ ++ struct mtd_oob_ops ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .datbuf = buf, ++ .len = len, ++ }; ++ int ret; ++ ++ ret = mtk_snand_mtd_read_oob(mtd, from, &ops); ++ ++ if (retlen) ++ *retlen = ops.retlen; ++ ++ return ret; ++} ++ ++static int mtk_snand_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, ++ size_t *retlen, const u_char *buf) ++{ ++ struct mtd_oob_ops ops = { ++ .mode = MTD_OPS_PLACE_OOB, ++ .datbuf = (void *)buf, ++ .len = len, ++ }; ++ int ret; ++ ++ ret = mtk_snand_mtd_write_oob(mtd, to, &ops); ++ ++ if (retlen) ++ *retlen = ops.retlen; ++ ++ return ret; ++} ++ ++static int mtk_snand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ ++ return mtk_snand_block_isbad(msm->snf, offs); ++} ++ ++static int mtk_snand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ ++ return mtk_snand_block_markbad(msm->snf, offs); ++} ++ ++static int mtk_snand_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *oobecc) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ ++ if (section) ++ return -ERANGE; ++ ++ oobecc->offset = msm->cinfo.fdm_size * msm->cinfo.num_sectors; ++ oobecc->length = mtd->oobsize - oobecc->offset; ++ ++ return 0; ++} ++ ++static int mtk_snand_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *oobfree) ++{ ++ struct mtk_snand_mtd *msm = mtd_to_msm(mtd); ++ ++ if (section >= msm->cinfo.num_sectors) ++ return -ERANGE; ++ ++ oobfree->length = msm->cinfo.fdm_size - 1; ++ oobfree->offset = section * msm->cinfo.fdm_size + 1; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops mtk_snand_ooblayout = { ++ .ecc = mtk_snand_ooblayout_ecc, ++ .rfree = mtk_snand_ooblayout_free, ++}; ++ ++static int mtk_snand_mtd_probe(struct udevice *dev) ++{ ++ struct mtk_snand_mtd *msm = dev_get_priv(dev); ++ struct mtd_info *mtd = dev_get_uclass_priv(dev); ++ struct mtk_snand_platdata mtk_snand_pdata = {}; ++ size_t namelen; ++ fdt_addr_t base; ++ int ret; ++ ++ base = dev_read_addr_name(dev, "nfi"); ++ if (base == FDT_ADDR_T_NONE) ++ return -EINVAL; ++ mtk_snand_pdata.nfi_base = map_sysmem(base, 0); ++ ++ base = dev_read_addr_name(dev, "ecc"); ++ if (base == FDT_ADDR_T_NONE) ++ return -EINVAL; ++ mtk_snand_pdata.ecc_base = map_sysmem(base, 0); ++ ++ mtk_snand_pdata.soc = dev_get_driver_data(dev); ++ mtk_snand_pdata.quad_spi = dev_read_bool(dev, "quad-spi"); ++ ++ ret = mtk_snand_init(NULL, &mtk_snand_pdata, &msm->snf); ++ if (ret) ++ return ret; ++ ++ mtk_snand_get_chip_info(msm->snf, &msm->cinfo); ++ ++ msm->page_cache = malloc(msm->cinfo.pagesize + msm->cinfo.sparesize); ++ if (!msm->page_cache) { ++ printf("%s: failed to allocate memory for page cache\n", ++ __func__); ++ ret = -ENOMEM; ++ goto errout1; ++ } ++ ++ namelen = sizeof(snand_mtd_name_prefix) + 12; ++ ++ mtd->name = malloc(namelen); ++ if (!mtd->name) { ++ printf("%s: failed to allocate memory for MTD name\n", ++ __func__); ++ ret = -ENOMEM; ++ goto errout2; ++ } ++ ++ msm->dev = dev; ++ ++ snprintf(mtd->name, namelen, "%s%u", snand_mtd_name_prefix, snandidx++); ++ ++ mtd->priv = msm; ++ mtd->dev = dev; ++ mtd->type = MTD_NANDFLASH; ++ mtd->flags = MTD_CAP_NANDFLASH; ++ ++ mtd->size = msm->cinfo.chipsize; ++ mtd->erasesize = msm->cinfo.blocksize; ++ mtd->writesize = msm->cinfo.pagesize; ++ mtd->writebufsize = mtd->writesize; ++ mtd->oobsize = msm->cinfo.sparesize; ++ mtd->oobavail = msm->cinfo.num_sectors * (msm->cinfo.fdm_size - 1); ++ ++ mtd->ooblayout = &mtk_snand_ooblayout; ++ ++ mtd->ecc_strength = msm->cinfo.ecc_strength; ++ mtd->bitflip_threshold = (mtd->ecc_strength * 3) / 4; ++ mtd->ecc_step_size = msm->cinfo.sector_size; ++ ++ mtd->_read = mtk_snand_mtd_read; ++ mtd->_write = mtk_snand_mtd_write; ++ mtd->_erase = mtk_snand_mtd_erase; ++ mtd->_read_oob = mtk_snand_mtd_read_oob; ++ mtd->_write_oob = mtk_snand_mtd_write_oob; ++ mtd->_block_isbad = mtk_snand_mtd_block_isbad; ++ mtd->_block_markbad = mtk_snand_mtd_block_markbad; ++ ++ ret = add_mtd_device(mtd); ++ if (ret) { ++ printf("%s: failed to add SPI-NAND MTD device\n", __func__); ++ ret = -ENODEV; ++ goto errout3; ++ } ++ ++ printf("SPI-NAND: %s (%lluMB)\n", msm->cinfo.model, ++ msm->cinfo.chipsize >> 20); ++ ++ return 0; ++ ++errout3: ++ free(mtd->name); ++ ++errout2: ++ free(msm->page_cache); ++ ++errout1: ++ mtk_snand_cleanup(msm->snf); ++ ++ return ret; ++} ++ ++static const struct udevice_id mtk_snand_ids[] = { ++ { .compatible = "mediatek,mt7622-snand", .data = SNAND_SOC_MT7622 }, ++ { .compatible = "mediatek,mt7629-snand", .data = SNAND_SOC_MT7629 }, ++ { .compatible = "mediatek,mt7981-snand", .data = SNAND_SOC_MT7981 }, ++ { .compatible = "mediatek,mt7986-snand", .data = SNAND_SOC_MT7986 }, ++ { /* sentinel */ }, ++}; ++ ++U_BOOT_DRIVER(spinand) = { ++ .name = "mtk-snand", ++ .id = UCLASS_MTD, ++ .of_match = mtk_snand_ids, ++ .priv_auto = sizeof(struct mtk_snand_mtd), ++ .probe = mtk_snand_mtd_probe, ++}; +--- a/drivers/mtd/mtk-snand/mtk-snand-os.c ++++ b/drivers/mtd/mtk-snand/mtk-snand-os.c +@@ -0,0 +1,39 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include "mtk-snand-def.h" ++ ++int mtk_snand_log(struct mtk_snand_plat_dev *pdev, ++ enum mtk_snand_log_category cat, const char *fmt, ...) ++{ ++ const char *catname = ""; ++ va_list ap; ++ int ret; ++ ++ switch (cat) { ++ case SNAND_LOG_NFI: ++ catname = "NFI: "; ++ break; ++ case SNAND_LOG_SNFI: ++ catname = "SNFI: "; ++ break; ++ case SNAND_LOG_ECC: ++ catname = "ECC: "; ++ break; ++ default: ++ break; ++ } ++ ++ puts("SPI-NAND: "); ++ puts(catname); ++ ++ va_start(ap, fmt); ++ ret = vprintf(fmt, ap); ++ va_end(ap); ++ ++ return ret; ++} +--- a/drivers/mtd/mtk-snand/mtk-snand-os.h ++++ b/drivers/mtd/mtk-snand/mtk-snand-os.h +@@ -0,0 +1,120 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _MTK_SNAND_OS_H_ ++#define _MTK_SNAND_OS_H_ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifndef ARCH_DMA_MINALIGN ++#define ARCH_DMA_MINALIGN 64 ++#endif ++ ++struct mtk_snand_plat_dev { ++ ulong unused; ++}; ++ ++/* Polling helpers */ ++#define read16_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ ++ readw_poll_timeout((addr), (val), (cond), (timeout_us)) ++ ++#define read32_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ ++ readl_poll_timeout((addr), (val), (cond), (timeout_us)) ++ ++/* Timer helpers */ ++typedef uint64_t mtk_snand_time_t; ++ ++static inline mtk_snand_time_t timer_get_ticks(void) ++{ ++ return get_ticks(); ++} ++ ++static inline mtk_snand_time_t timer_time_to_tick(uint32_t timeout_us) ++{ ++ return usec_to_tick(timeout_us); ++} ++ ++static inline bool timer_is_timeout(mtk_snand_time_t start_tick, ++ mtk_snand_time_t timeout_tick) ++{ ++ return get_ticks() - start_tick > timeout_tick; ++} ++ ++/* Memory helpers */ ++static inline void *generic_mem_alloc(struct mtk_snand_plat_dev *pdev, ++ size_t size) ++{ ++ return calloc(1, size); ++} ++ ++static inline void generic_mem_free(struct mtk_snand_plat_dev *pdev, void *ptr) ++{ ++ free(ptr); ++} ++ ++static inline void *dma_mem_alloc(struct mtk_snand_plat_dev *pdev, size_t size) ++{ ++ return memalign(ARCH_DMA_MINALIGN, size); ++} ++ ++static inline void dma_mem_free(struct mtk_snand_plat_dev *pdev, void *ptr) ++{ ++ free(ptr); ++} ++ ++static inline int dma_mem_map(struct mtk_snand_plat_dev *pdev, void *vaddr, ++ uintptr_t *dma_addr, size_t size, bool to_device) ++{ ++ size_t cachelen = roundup(size, ARCH_DMA_MINALIGN); ++ uintptr_t endaddr = (uintptr_t)vaddr + cachelen; ++ ++ if (to_device) ++ flush_dcache_range((uintptr_t)vaddr, endaddr); ++ else ++ invalidate_dcache_range((uintptr_t)vaddr, endaddr); ++ ++ *dma_addr = (uintptr_t)vaddr; ++ ++ return 0; ++} ++ ++static inline void dma_mem_unmap(struct mtk_snand_plat_dev *pdev, ++ uintptr_t dma_addr, size_t size, ++ bool to_device) ++{ ++} ++ ++/* Interrupt helpers */ ++static inline void irq_completion_done(struct mtk_snand_plat_dev *pdev) ++{ ++} ++ ++static inline void irq_completion_init(struct mtk_snand_plat_dev *pdev) ++{ ++} ++ ++static inline int irq_completion_wait(struct mtk_snand_plat_dev *pdev, ++ void __iomem *reg, uint32_t bit, ++ uint32_t timeout_us) ++{ ++ uint32_t val; ++ ++ return read32_poll_timeout(reg, val, val & bit, 0, timeout_us); ++} ++ ++#endif /* _MTK_SNAND_OS_H_ */ +--- a/drivers/mtd/mtk-snand/mtk-snand.c ++++ b/drivers/mtd/mtk-snand/mtk-snand.c +@@ -0,0 +1,1933 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include "mtk-snand-def.h" ++ ++/* NFI registers */ ++#define NFI_CNFG 0x000 ++#define CNFG_OP_MODE_S 12 ++#define CNFG_OP_MODE_CUST 6 ++#define CNFG_OP_MODE_PROGRAM 3 ++#define CNFG_AUTO_FMT_EN BIT(9) ++#define CNFG_HW_ECC_EN BIT(8) ++#define CNFG_DMA_BURST_EN BIT(2) ++#define CNFG_READ_MODE BIT(1) ++#define CNFG_DMA_MODE BIT(0) ++ ++#define NFI_PAGEFMT 0x0004 ++#define NFI_SPARE_SIZE_LS_S 16 ++#define NFI_FDM_ECC_NUM_S 12 ++#define NFI_FDM_NUM_S 8 ++#define NFI_SPARE_SIZE_S 4 ++#define NFI_SEC_SEL_512 BIT(2) ++#define NFI_PAGE_SIZE_S 0 ++#define NFI_PAGE_SIZE_512_2K 0 ++#define NFI_PAGE_SIZE_2K_4K 1 ++#define NFI_PAGE_SIZE_4K_8K 2 ++#define NFI_PAGE_SIZE_8K_16K 3 ++ ++#define NFI_CON 0x008 ++#define CON_SEC_NUM_S 12 ++#define CON_BWR BIT(9) ++#define CON_BRD BIT(8) ++#define CON_NFI_RST BIT(1) ++#define CON_FIFO_FLUSH BIT(0) ++ ++#define NFI_INTR_EN 0x010 ++#define NFI_INTR_STA 0x014 ++#define NFI_IRQ_INTR_EN BIT(31) ++#define NFI_IRQ_CUS_READ BIT(8) ++#define NFI_IRQ_CUS_PG BIT(7) ++ ++#define NFI_CMD 0x020 ++ ++#define NFI_STRDATA 0x040 ++#define STR_DATA BIT(0) ++ ++#define NFI_STA 0x060 ++#define NFI_NAND_FSM GENMASK(28, 24) ++#define NFI_FSM GENMASK(19, 16) ++#define READ_EMPTY BIT(12) ++ ++#define NFI_FIFOSTA 0x064 ++#define FIFO_WR_REMAIN_S 8 ++#define FIFO_RD_REMAIN_S 0 ++ ++#define NFI_ADDRCNTR 0x070 ++#define SEC_CNTR GENMASK(16, 12) ++#define SEC_CNTR_S 12 ++#define NFI_SEC_CNTR(val) (((val) & SEC_CNTR) >> SEC_CNTR_S) ++ ++#define NFI_STRADDR 0x080 ++ ++#define NFI_BYTELEN 0x084 ++#define BUS_SEC_CNTR(val) (((val) & SEC_CNTR) >> SEC_CNTR_S) ++ ++#define NFI_FDM0L 0x0a0 ++#define NFI_FDM0M 0x0a4 ++#define NFI_FDML(n) (NFI_FDM0L + (n) * 8) ++#define NFI_FDMM(n) (NFI_FDM0M + (n) * 8) ++ ++#define NFI_DEBUG_CON1 0x220 ++#define WBUF_EN BIT(2) ++ ++#define NFI_MASTERSTA 0x224 ++#define MAS_ADDR GENMASK(11, 9) ++#define MAS_RD GENMASK(8, 6) ++#define MAS_WR GENMASK(5, 3) ++#define MAS_RDDLY GENMASK(2, 0) ++#define NFI_MASTERSTA_MASK_7622 (MAS_ADDR | MAS_RD | MAS_WR | MAS_RDDLY) ++#define AHB_BUS_BUSY BIT(1) ++#define BUS_BUSY BIT(0) ++#define NFI_MASTERSTA_MASK_7981 (AHB_BUS_BUSY | BUS_BUSY) ++#define NFI_MASTERSTA_MASK_7986 (AHB_BUS_BUSY | BUS_BUSY) ++ ++/* SNFI registers */ ++#define SNF_MAC_CTL 0x500 ++#define MAC_XIO_SEL BIT(4) ++#define SF_MAC_EN BIT(3) ++#define SF_TRIG BIT(2) ++#define WIP_READY BIT(1) ++#define WIP BIT(0) ++ ++#define SNF_MAC_OUTL 0x504 ++#define SNF_MAC_INL 0x508 ++ ++#define SNF_RD_CTL2 0x510 ++#define DATA_READ_DUMMY_S 8 ++#define DATA_READ_CMD_S 0 ++ ++#define SNF_RD_CTL3 0x514 ++ ++#define SNF_PG_CTL1 0x524 ++#define PG_LOAD_CMD_S 8 ++ ++#define SNF_PG_CTL2 0x528 ++ ++#define SNF_MISC_CTL 0x538 ++#define SW_RST BIT(28) ++#define FIFO_RD_LTC_S 25 ++#define PG_LOAD_X4_EN BIT(20) ++#define DATA_READ_MODE_S 16 ++#define DATA_READ_MODE GENMASK(18, 16) ++#define DATA_READ_MODE_X1 0 ++#define DATA_READ_MODE_X2 1 ++#define DATA_READ_MODE_X4 2 ++#define DATA_READ_MODE_DUAL 5 ++#define DATA_READ_MODE_QUAD 6 ++#define LATCH_LAT_S 8 ++#define LATCH_LAT GENMASK(9, 8) ++#define PG_LOAD_CUSTOM_EN BIT(7) ++#define DATARD_CUSTOM_EN BIT(6) ++#define CS_DESELECT_CYC_S 0 ++ ++#define SNF_MISC_CTL2 0x53c ++#define PROGRAM_LOAD_BYTE_NUM_S 16 ++#define READ_DATA_BYTE_NUM_S 11 ++ ++#define SNF_DLY_CTL3 0x548 ++#define SFCK_SAM_DLY_S 0 ++ ++#define SNF_STA_CTL1 0x550 ++#define CUS_PG_DONE BIT(28) ++#define CUS_READ_DONE BIT(27) ++#define SPI_STATE_S 0 ++#define SPI_STATE GENMASK(3, 0) ++ ++#define SNF_CFG 0x55c ++#define SPI_MODE BIT(0) ++ ++#define SNF_GPRAM 0x800 ++#define SNF_GPRAM_SIZE 0xa0 ++ ++#define SNFI_POLL_INTERVAL 1000000 ++ ++static const uint8_t mt7622_spare_sizes[] = { 16, 26, 27, 28 }; ++ ++static const uint8_t mt7981_spare_sizes[] = { ++ 16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 61, 63, 64, ++ 67, 74 ++}; ++ ++static const uint8_t mt7986_spare_sizes[] = { ++ 16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 61, 63, 64, ++ 67, 74 ++}; ++ ++static const struct mtk_snand_soc_data mtk_snand_socs[__SNAND_SOC_MAX] = { ++ [SNAND_SOC_MT7622] = { ++ .sector_size = 512, ++ .max_sectors = 8, ++ .fdm_size = 8, ++ .fdm_ecc_size = 1, ++ .fifo_size = 32, ++ .bbm_swap = false, ++ .empty_page_check = false, ++ .mastersta_mask = NFI_MASTERSTA_MASK_7622, ++ .spare_sizes = mt7622_spare_sizes, ++ .num_spare_size = ARRAY_SIZE(mt7622_spare_sizes), ++ .latch_lat = 0, ++ .sample_delay = 40 ++ }, ++ [SNAND_SOC_MT7629] = { ++ .sector_size = 512, ++ .max_sectors = 8, ++ .fdm_size = 8, ++ .fdm_ecc_size = 1, ++ .fifo_size = 32, ++ .bbm_swap = true, ++ .empty_page_check = false, ++ .mastersta_mask = NFI_MASTERSTA_MASK_7622, ++ .spare_sizes = mt7622_spare_sizes, ++ .num_spare_size = ARRAY_SIZE(mt7622_spare_sizes), ++ .latch_lat = 0, ++ .sample_delay = 40 ++ }, ++ [SNAND_SOC_MT7981] = { ++ .sector_size = 1024, ++ .max_sectors = 16, ++ .fdm_size = 8, ++ .fdm_ecc_size = 1, ++ .fifo_size = 64, ++ .bbm_swap = true, ++ .empty_page_check = true, ++ .mastersta_mask = NFI_MASTERSTA_MASK_7981, ++ .spare_sizes = mt7981_spare_sizes, ++ .num_spare_size = ARRAY_SIZE(mt7981_spare_sizes), ++ .latch_lat = 0, ++ .sample_delay = 40 ++ }, ++ [SNAND_SOC_MT7986] = { ++ .sector_size = 1024, ++ .max_sectors = 16, ++ .fdm_size = 8, ++ .fdm_ecc_size = 1, ++ .fifo_size = 64, ++ .bbm_swap = true, ++ .empty_page_check = true, ++ .mastersta_mask = NFI_MASTERSTA_MASK_7986, ++ .spare_sizes = mt7986_spare_sizes, ++ .num_spare_size = ARRAY_SIZE(mt7986_spare_sizes), ++ .latch_lat = 0, ++ .sample_delay = 40 ++ }, ++}; ++ ++static inline uint32_t nfi_read32(struct mtk_snand *snf, uint32_t reg) ++{ ++ return readl(snf->nfi_base + reg); ++} ++ ++static inline void nfi_write32(struct mtk_snand *snf, uint32_t reg, ++ uint32_t val) ++{ ++ writel(val, snf->nfi_base + reg); ++} ++ ++static inline void nfi_write16(struct mtk_snand *snf, uint32_t reg, ++ uint16_t val) ++{ ++ writew(val, snf->nfi_base + reg); ++} ++ ++static inline void nfi_rmw32(struct mtk_snand *snf, uint32_t reg, uint32_t clr, ++ uint32_t set) ++{ ++ uint32_t val; ++ ++ val = readl(snf->nfi_base + reg); ++ val &= ~clr; ++ val |= set; ++ writel(val, snf->nfi_base + reg); ++} ++ ++static void nfi_write_data(struct mtk_snand *snf, uint32_t reg, ++ const uint8_t *data, uint32_t len) ++{ ++ uint32_t i, val = 0, es = sizeof(uint32_t); ++ ++ for (i = reg; i < reg + len; i++) { ++ val |= ((uint32_t)*data++) << (8 * (i % es)); ++ ++ if (i % es == es - 1 || i == reg + len - 1) { ++ nfi_write32(snf, i & ~(es - 1), val); ++ val = 0; ++ } ++ } ++} ++ ++static void nfi_read_data(struct mtk_snand *snf, uint32_t reg, uint8_t *data, ++ uint32_t len) ++{ ++ uint32_t i, val = 0, es = sizeof(uint32_t); ++ ++ for (i = reg; i < reg + len; i++) { ++ if (i == reg || i % es == 0) ++ val = nfi_read32(snf, i & ~(es - 1)); ++ ++ *data++ = (uint8_t)(val >> (8 * (i % es))); ++ } ++} ++ ++static inline void do_bm_swap(uint8_t *bm1, uint8_t *bm2) ++{ ++ uint8_t tmp = *bm1; ++ *bm1 = *bm2; ++ *bm2 = tmp; ++} ++ ++static void mtk_snand_bm_swap_raw(struct mtk_snand *snf) ++{ ++ uint32_t fdm_bbm_pos; ++ ++ if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1) ++ return; ++ ++ fdm_bbm_pos = (snf->ecc_steps - 1) * snf->raw_sector_size + ++ snf->nfi_soc->sector_size; ++ do_bm_swap(&snf->page_cache[fdm_bbm_pos], ++ &snf->page_cache[snf->writesize]); ++} ++ ++static void mtk_snand_bm_swap(struct mtk_snand *snf) ++{ ++ uint32_t buf_bbm_pos, fdm_bbm_pos; ++ ++ if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1) ++ return; ++ ++ buf_bbm_pos = snf->writesize - ++ (snf->ecc_steps - 1) * snf->spare_per_sector; ++ fdm_bbm_pos = snf->writesize + ++ (snf->ecc_steps - 1) * snf->nfi_soc->fdm_size; ++ do_bm_swap(&snf->page_cache[fdm_bbm_pos], ++ &snf->page_cache[buf_bbm_pos]); ++} ++ ++static void mtk_snand_fdm_bm_swap_raw(struct mtk_snand *snf) ++{ ++ uint32_t fdm_bbm_pos1, fdm_bbm_pos2; ++ ++ if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1) ++ return; ++ ++ fdm_bbm_pos1 = snf->nfi_soc->sector_size; ++ fdm_bbm_pos2 = (snf->ecc_steps - 1) * snf->raw_sector_size + ++ snf->nfi_soc->sector_size; ++ do_bm_swap(&snf->page_cache[fdm_bbm_pos1], ++ &snf->page_cache[fdm_bbm_pos2]); ++} ++ ++static void mtk_snand_fdm_bm_swap(struct mtk_snand *snf) ++{ ++ uint32_t fdm_bbm_pos1, fdm_bbm_pos2; ++ ++ if (!snf->nfi_soc->bbm_swap || snf->ecc_steps == 1) ++ return; ++ ++ fdm_bbm_pos1 = snf->writesize; ++ fdm_bbm_pos2 = snf->writesize + ++ (snf->ecc_steps - 1) * snf->nfi_soc->fdm_size; ++ do_bm_swap(&snf->page_cache[fdm_bbm_pos1], ++ &snf->page_cache[fdm_bbm_pos2]); ++} ++ ++static int mtk_nfi_reset(struct mtk_snand *snf) ++{ ++ uint32_t val, fifo_mask; ++ int ret; ++ ++ nfi_write32(snf, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST); ++ ++ ret = read16_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val, ++ !(val & snf->nfi_soc->mastersta_mask), 0, ++ SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "NFI master is still busy after reset\n"); ++ return ret; ++ } ++ ++ ret = read32_poll_timeout(snf->nfi_base + NFI_STA, val, ++ !(val & (NFI_FSM | NFI_NAND_FSM)), 0, ++ SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, "Failed to reset NFI\n"); ++ return ret; ++ } ++ ++ fifo_mask = ((snf->nfi_soc->fifo_size - 1) << FIFO_RD_REMAIN_S) | ++ ((snf->nfi_soc->fifo_size - 1) << FIFO_WR_REMAIN_S); ++ ret = read16_poll_timeout(snf->nfi_base + NFI_FIFOSTA, val, ++ !(val & fifo_mask), 0, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, "NFI FIFOs are not empty\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int mtk_snand_mac_reset(struct mtk_snand *snf) ++{ ++ int ret; ++ uint32_t val; ++ ++ nfi_rmw32(snf, SNF_MISC_CTL, 0, SW_RST); ++ ++ ret = read32_poll_timeout(snf->nfi_base + SNF_STA_CTL1, val, ++ !(val & SPI_STATE), 0, SNFI_POLL_INTERVAL); ++ if (ret) ++ snand_log_snfi(snf->pdev, "Failed to reset SNFI MAC\n"); ++ ++ nfi_write32(snf, SNF_MISC_CTL, (2 << FIFO_RD_LTC_S) | ++ (10 << CS_DESELECT_CYC_S) | (snf->nfi_soc->latch_lat << LATCH_LAT_S)); ++ ++ return ret; ++} ++ ++static int mtk_snand_mac_trigger(struct mtk_snand *snf, uint32_t outlen, ++ uint32_t inlen) ++{ ++ int ret; ++ uint32_t val; ++ ++ nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN); ++ nfi_write32(snf, SNF_MAC_OUTL, outlen); ++ nfi_write32(snf, SNF_MAC_INL, inlen); ++ ++ nfi_write32(snf, SNF_MAC_CTL, SF_MAC_EN | SF_TRIG); ++ ++ ret = read32_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val, ++ val & WIP_READY, 0, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_snfi(snf->pdev, "Timed out waiting for WIP_READY\n"); ++ goto cleanup; ++ } ++ ++ ret = read32_poll_timeout(snf->nfi_base + SNF_MAC_CTL, val, ++ !(val & WIP), 0, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_snfi(snf->pdev, ++ "Timed out waiting for WIP cleared\n"); ++ } ++ ++cleanup: ++ nfi_write32(snf, SNF_MAC_CTL, 0); ++ ++ return ret; ++} ++ ++int mtk_snand_mac_io(struct mtk_snand *snf, const uint8_t *out, uint32_t outlen, ++ uint8_t *in, uint32_t inlen) ++{ ++ int ret; ++ ++ if (outlen + inlen > SNF_GPRAM_SIZE) ++ return -EINVAL; ++ ++ mtk_snand_mac_reset(snf); ++ ++ nfi_write_data(snf, SNF_GPRAM, out, outlen); ++ ++ ret = mtk_snand_mac_trigger(snf, outlen, inlen); ++ if (ret) ++ return ret; ++ ++ if (!inlen) ++ return 0; ++ ++ nfi_read_data(snf, SNF_GPRAM + outlen, in, inlen); ++ ++ return 0; ++} ++ ++static int mtk_snand_get_feature(struct mtk_snand *snf, uint32_t addr) ++{ ++ uint8_t op[2], val; ++ int ret; ++ ++ op[0] = SNAND_CMD_GET_FEATURE; ++ op[1] = (uint8_t)addr; ++ ++ ret = mtk_snand_mac_io(snf, op, sizeof(op), &val, 1); ++ if (ret) ++ return ret; ++ ++ return val; ++} ++ ++int mtk_snand_set_feature(struct mtk_snand *snf, uint32_t addr, uint32_t val) ++{ ++ uint8_t op[3]; ++ ++ op[0] = SNAND_CMD_SET_FEATURE; ++ op[1] = (uint8_t)addr; ++ op[2] = (uint8_t)val; ++ ++ return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0); ++} ++ ++static int mtk_snand_poll_status(struct mtk_snand *snf, uint32_t wait_us) ++{ ++ int val; ++ mtk_snand_time_t time_start, tmo; ++ ++ time_start = timer_get_ticks(); ++ tmo = timer_time_to_tick(wait_us); ++ ++ do { ++ val = mtk_snand_get_feature(snf, SNAND_FEATURE_STATUS_ADDR); ++ if (!(val & SNAND_STATUS_OIP)) ++ return val & (SNAND_STATUS_ERASE_FAIL | ++ SNAND_STATUS_PROGRAM_FAIL); ++ } while (!timer_is_timeout(time_start, tmo)); ++ ++ return -ETIMEDOUT; ++} ++ ++int mtk_snand_chip_reset(struct mtk_snand *snf) ++{ ++ uint8_t op = SNAND_CMD_RESET; ++ int ret; ++ ++ ret = mtk_snand_mac_io(snf, &op, 1, NULL, 0); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int mtk_snand_config_feature(struct mtk_snand *snf, uint8_t clr, ++ uint8_t set) ++{ ++ int val, newval; ++ int ret; ++ ++ val = mtk_snand_get_feature(snf, SNAND_FEATURE_CONFIG_ADDR); ++ if (val < 0) { ++ snand_log_chip(snf->pdev, ++ "Failed to get configuration feature\n"); ++ return val; ++ } ++ ++ newval = (val & (~clr)) | set; ++ ++ if (newval == val) ++ return 0; ++ ++ ret = mtk_snand_set_feature(snf, SNAND_FEATURE_CONFIG_ADDR, ++ (uint8_t)newval); ++ if (val < 0) { ++ snand_log_chip(snf->pdev, ++ "Failed to set configuration feature\n"); ++ return ret; ++ } ++ ++ val = mtk_snand_get_feature(snf, SNAND_FEATURE_CONFIG_ADDR); ++ if (val < 0) { ++ snand_log_chip(snf->pdev, ++ "Failed to get configuration feature\n"); ++ return val; ++ } ++ ++ if (newval != val) ++ return -ENOTSUPP; ++ ++ return 0; ++} ++ ++static int mtk_snand_ondie_ecc_control(struct mtk_snand *snf, bool enable) ++{ ++ int ret; ++ ++ if (enable) ++ ret = mtk_snand_config_feature(snf, 0, SNAND_FEATURE_ECC_EN); ++ else ++ ret = mtk_snand_config_feature(snf, SNAND_FEATURE_ECC_EN, 0); ++ ++ if (ret) { ++ snand_log_chip(snf->pdev, "Failed to %s On-Die ECC engine\n", ++ enable ? "enable" : "disable"); ++ } ++ ++ return ret; ++} ++ ++static int mtk_snand_qspi_control(struct mtk_snand *snf, bool enable) ++{ ++ int ret; ++ ++ if (enable) { ++ ret = mtk_snand_config_feature(snf, 0, ++ SNAND_FEATURE_QUAD_ENABLE); ++ } else { ++ ret = mtk_snand_config_feature(snf, ++ SNAND_FEATURE_QUAD_ENABLE, 0); ++ } ++ ++ if (ret) { ++ snand_log_chip(snf->pdev, "Failed to %s quad spi\n", ++ enable ? "enable" : "disable"); ++ } ++ ++ return ret; ++} ++ ++static int mtk_snand_unlock(struct mtk_snand *snf) ++{ ++ int ret; ++ ++ ret = mtk_snand_set_feature(snf, SNAND_FEATURE_PROTECT_ADDR, 0); ++ if (ret) { ++ snand_log_chip(snf->pdev, "Failed to set protection feature\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int mtk_snand_write_enable(struct mtk_snand *snf) ++{ ++ uint8_t op = SNAND_CMD_WRITE_ENABLE; ++ int ret, val; ++ ++ ret = mtk_snand_mac_io(snf, &op, 1, NULL, 0); ++ if (ret) ++ return ret; ++ ++ val = mtk_snand_get_feature(snf, SNAND_FEATURE_STATUS_ADDR); ++ if (val < 0) ++ return ret; ++ ++ if (val & SNAND_STATUS_WEL) ++ return 0; ++ ++ snand_log_chip(snf->pdev, "Failed to send write-enable command\n"); ++ ++ return -ENOTSUPP; ++} ++ ++static int mtk_snand_select_die(struct mtk_snand *snf, uint32_t dieidx) ++{ ++ if (!snf->select_die) ++ return 0; ++ ++ return snf->select_die(snf, dieidx); ++} ++ ++static uint64_t mtk_snand_select_die_address(struct mtk_snand *snf, ++ uint64_t addr) ++{ ++ uint32_t dieidx; ++ ++ if (!snf->select_die) ++ return addr; ++ ++ dieidx = addr >> snf->die_shift; ++ ++ mtk_snand_select_die(snf, dieidx); ++ ++ return addr & snf->die_mask; ++} ++ ++static uint32_t mtk_snand_get_plane_address(struct mtk_snand *snf, ++ uint32_t page) ++{ ++ uint32_t pages_per_block; ++ ++ pages_per_block = 1 << (snf->erasesize_shift - snf->writesize_shift); ++ ++ if (page & pages_per_block) ++ return 1 << (snf->writesize_shift + 1); ++ ++ return 0; ++} ++ ++static int mtk_snand_page_op(struct mtk_snand *snf, uint32_t page, uint8_t cmd) ++{ ++ uint8_t op[4]; ++ ++ op[0] = cmd; ++ op[1] = (page >> 16) & 0xff; ++ op[2] = (page >> 8) & 0xff; ++ op[3] = page & 0xff; ++ ++ return mtk_snand_mac_io(snf, op, sizeof(op), NULL, 0); ++} ++ ++static void mtk_snand_read_fdm(struct mtk_snand *snf, uint8_t *buf) ++{ ++ uint32_t vall, valm; ++ uint8_t *oobptr = buf; ++ int i, j; ++ ++ for (i = 0; i < snf->ecc_steps; i++) { ++ vall = nfi_read32(snf, NFI_FDML(i)); ++ valm = nfi_read32(snf, NFI_FDMM(i)); ++ ++ for (j = 0; j < snf->nfi_soc->fdm_size; j++) ++ oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8); ++ ++ oobptr += snf->nfi_soc->fdm_size; ++ } ++} ++ ++static int mtk_snand_read_ecc_parity(struct mtk_snand *snf, uint32_t page, ++ uint32_t sect, uint8_t *oob) ++{ ++ uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size; ++ uint32_t coladdr, raw_offs, offs; ++ uint8_t op[4]; ++ ++ if (sizeof(op) + ecc_bytes > SNF_GPRAM_SIZE) { ++ snand_log_snfi(snf->pdev, ++ "ECC parity size does not fit the GPRAM\n"); ++ return -ENOTSUPP; ++ } ++ ++ raw_offs = sect * snf->raw_sector_size + snf->nfi_soc->sector_size + ++ snf->nfi_soc->fdm_size; ++ offs = snf->ecc_steps * snf->nfi_soc->fdm_size + sect * ecc_bytes; ++ ++ /* Column address with plane bit */ ++ coladdr = raw_offs | mtk_snand_get_plane_address(snf, page); ++ ++ op[0] = SNAND_CMD_READ_FROM_CACHE; ++ op[1] = (coladdr >> 8) & 0xff; ++ op[2] = coladdr & 0xff; ++ op[3] = 0; ++ ++ return mtk_snand_mac_io(snf, op, sizeof(op), oob + offs, ecc_bytes); ++} ++ ++static int mtk_snand_check_ecc_result(struct mtk_snand *snf, uint32_t page) ++{ ++ uint8_t *oob = snf->page_cache + snf->writesize; ++ int i, rc, ret = 0, max_bitflips = 0; ++ ++ for (i = 0; i < snf->ecc_steps; i++) { ++ if (snf->sect_bf[i] >= 0) { ++ if (snf->sect_bf[i] > max_bitflips) ++ max_bitflips = snf->sect_bf[i]; ++ continue; ++ } ++ ++ rc = mtk_snand_read_ecc_parity(snf, page, i, oob); ++ if (rc) ++ return rc; ++ ++ rc = mtk_ecc_fixup_empty_sector(snf, i); ++ if (rc < 0) { ++ ret = -EBADMSG; ++ ++ snand_log_ecc(snf->pdev, ++ "Uncorrectable bitflips in page %u sect %u\n", ++ page, i); ++ } else if (rc) { ++ snf->sect_bf[i] = rc; ++ ++ if (snf->sect_bf[i] > max_bitflips) ++ max_bitflips = snf->sect_bf[i]; ++ ++ snand_log_ecc(snf->pdev, ++ "%u bitflip%s corrected in page %u sect %u\n", ++ rc, rc > 1 ? "s" : "", page, i); ++ } else { ++ snf->sect_bf[i] = 0; ++ } ++ } ++ ++ return ret ? ret : max_bitflips; ++} ++ ++static int mtk_snand_read_cache(struct mtk_snand *snf, uint32_t page, bool raw) ++{ ++ uint32_t coladdr, rwbytes, mode, len, val; ++ uintptr_t dma_addr; ++ int ret; ++ ++ /* Column address with plane bit */ ++ coladdr = mtk_snand_get_plane_address(snf, page); ++ ++ mtk_snand_mac_reset(snf); ++ mtk_nfi_reset(snf); ++ ++ /* Command and dummy cycles */ ++ nfi_write32(snf, SNF_RD_CTL2, ++ ((uint32_t)snf->dummy_rfc << DATA_READ_DUMMY_S) | ++ (snf->opcode_rfc << DATA_READ_CMD_S)); ++ ++ /* Column address */ ++ nfi_write32(snf, SNF_RD_CTL3, coladdr); ++ ++ /* Set read mode */ ++ mode = (uint32_t)snf->mode_rfc << DATA_READ_MODE_S; ++ nfi_rmw32(snf, SNF_MISC_CTL, DATA_READ_MODE, ++ mode | DATARD_CUSTOM_EN | (snf->nfi_soc->latch_lat << LATCH_LAT_S)); ++ ++ /* Set bytes to read */ ++ rwbytes = snf->ecc_steps * snf->raw_sector_size; ++ nfi_write32(snf, SNF_MISC_CTL2, (rwbytes << PROGRAM_LOAD_BYTE_NUM_S) | ++ rwbytes); ++ ++ /* NFI read prepare */ ++ mode = raw ? 0 : CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN; ++ nfi_write16(snf, NFI_CNFG, (CNFG_OP_MODE_CUST << CNFG_OP_MODE_S) | ++ CNFG_DMA_BURST_EN | CNFG_READ_MODE | CNFG_DMA_MODE | mode); ++ ++ nfi_write32(snf, NFI_CON, (snf->ecc_steps << CON_SEC_NUM_S)); ++ ++ /* Prepare for DMA read */ ++ len = snf->writesize + snf->oobsize; ++ ret = dma_mem_map(snf->pdev, snf->page_cache, &dma_addr, len, false); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "DMA map from device failed with %d\n", ret); ++ return ret; ++ } ++ ++ nfi_write32(snf, NFI_STRADDR, (uint32_t)dma_addr); ++ ++ if (!raw) ++ mtk_snand_ecc_decoder_start(snf); ++ ++ /* Prepare for custom read interrupt */ ++ nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_READ); ++ irq_completion_init(snf->pdev); ++ ++ /* Trigger NFI into custom mode */ ++ nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_READ); ++ ++ /* Start DMA read */ ++ nfi_rmw32(snf, NFI_CON, 0, CON_BRD); ++ nfi_write16(snf, NFI_STRDATA, STR_DATA); ++ ++ /* Wait for operation finished */ ++ ret = irq_completion_wait(snf->pdev, snf->nfi_base + SNF_STA_CTL1, ++ CUS_READ_DONE, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "DMA timed out for reading from cache\n"); ++ goto cleanup; ++ } ++ ++ /* Wait for BUS_SEC_CNTR returning expected value */ ++ ret = read32_poll_timeout(snf->nfi_base + NFI_BYTELEN, val, ++ BUS_SEC_CNTR(val) >= snf->ecc_steps, ++ 0, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "Timed out waiting for BUS_SEC_CNTR\n"); ++ goto cleanup; ++ } ++ ++ /* Wait for bus becoming idle */ ++ ret = read32_poll_timeout(snf->nfi_base + NFI_MASTERSTA, val, ++ !(val & snf->nfi_soc->mastersta_mask), ++ 0, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "Timed out waiting for bus becoming idle\n"); ++ goto cleanup; ++ } ++ ++ if (!raw) { ++ ret = mtk_ecc_wait_decoder_done(snf); ++ if (ret) ++ goto cleanup; ++ ++ mtk_snand_read_fdm(snf, snf->page_cache + snf->writesize); ++ ++ mtk_ecc_check_decode_error(snf); ++ mtk_snand_ecc_decoder_stop(snf); ++ ++ ret = mtk_snand_check_ecc_result(snf, page); ++ } ++ ++cleanup: ++ /* DMA cleanup */ ++ dma_mem_unmap(snf->pdev, dma_addr, len, false); ++ ++ /* Stop read */ ++ nfi_write32(snf, NFI_CON, 0); ++ nfi_write16(snf, NFI_CNFG, 0); ++ ++ /* Clear SNF done flag */ ++ nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE); ++ nfi_write32(snf, SNF_STA_CTL1, 0); ++ ++ /* Disable interrupt */ ++ nfi_read32(snf, NFI_INTR_STA); ++ nfi_write32(snf, NFI_INTR_EN, 0); ++ ++ nfi_rmw32(snf, SNF_MISC_CTL, DATARD_CUSTOM_EN | LATCH_LAT, 0); ++ ++ return ret; ++} ++ ++static void mtk_snand_from_raw_page(struct mtk_snand *snf, void *buf, void *oob) ++{ ++ uint32_t i, ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size; ++ uint8_t *eccptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size; ++ uint8_t *bufptr = buf, *oobptr = oob, *raw_sector; ++ ++ for (i = 0; i < snf->ecc_steps; i++) { ++ raw_sector = snf->page_cache + i * snf->raw_sector_size; ++ ++ if (buf) { ++ memcpy(bufptr, raw_sector, snf->nfi_soc->sector_size); ++ bufptr += snf->nfi_soc->sector_size; ++ } ++ ++ raw_sector += snf->nfi_soc->sector_size; ++ ++ if (oob) { ++ memcpy(oobptr, raw_sector, snf->nfi_soc->fdm_size); ++ oobptr += snf->nfi_soc->fdm_size; ++ raw_sector += snf->nfi_soc->fdm_size; ++ ++ memcpy(eccptr, raw_sector, ecc_bytes); ++ eccptr += ecc_bytes; ++ } ++ } ++} ++ ++static int mtk_snand_do_read_page(struct mtk_snand *snf, uint64_t addr, ++ void *buf, void *oob, bool raw, bool format) ++{ ++ uint64_t die_addr; ++ uint32_t page, dly_ctrl3; ++ int ret, retry_cnt = 0; ++ ++ die_addr = mtk_snand_select_die_address(snf, addr); ++ page = die_addr >> snf->writesize_shift; ++ ++ dly_ctrl3 = nfi_read32(snf, SNF_DLY_CTL3); ++ ++ ret = mtk_snand_page_op(snf, page, SNAND_CMD_READ_TO_CACHE); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL); ++ if (ret < 0) { ++ snand_log_chip(snf->pdev, "Read to cache command timed out\n"); ++ return ret; ++ } ++ ++retry: ++ ret = mtk_snand_read_cache(snf, page, raw); ++ if (ret < 0 && ret != -EBADMSG) ++ return ret; ++ ++ if (ret == -EBADMSG && retry_cnt < 16) { ++ nfi_write32(snf, SNF_DLY_CTL3, retry_cnt * 2); ++ retry_cnt++; ++ goto retry; ++ } ++ ++ if (retry_cnt) { ++ if(ret == -EBADMSG) { ++ nfi_write32(snf, SNF_DLY_CTL3, dly_ctrl3); ++ snand_log_chip(snf->pdev, ++ "NFI calibration failed. Original sample delay: 0x%x\n", ++ dly_ctrl3); ++ } else { ++ snand_log_chip(snf->pdev, ++ "NFI calibration passed. New sample delay: 0x%x\n", ++ nfi_read32(snf, SNF_DLY_CTL3)); ++ } ++ } ++ ++ if (raw) { ++ if (format) { ++ mtk_snand_bm_swap_raw(snf); ++ mtk_snand_fdm_bm_swap_raw(snf); ++ mtk_snand_from_raw_page(snf, buf, oob); ++ } else { ++ if (buf) ++ memcpy(buf, snf->page_cache, snf->writesize); ++ ++ if (oob) { ++ memset(oob, 0xff, snf->oobsize); ++ memcpy(oob, snf->page_cache + snf->writesize, ++ snf->ecc_steps * snf->spare_per_sector); ++ } ++ } ++ } else { ++ mtk_snand_bm_swap(snf); ++ mtk_snand_fdm_bm_swap(snf); ++ ++ if (buf) ++ memcpy(buf, snf->page_cache, snf->writesize); ++ ++ if (oob) { ++ memset(oob, 0xff, snf->oobsize); ++ memcpy(oob, snf->page_cache + snf->writesize, ++ snf->ecc_steps * snf->nfi_soc->fdm_size); ++ } ++ } ++ ++ return ret; ++} ++ ++int mtk_snand_read_page(struct mtk_snand *snf, uint64_t addr, void *buf, ++ void *oob, bool raw) ++{ ++ if (!snf || (!buf && !oob)) ++ return -EINVAL; ++ ++ if (addr >= snf->size) ++ return -EINVAL; ++ ++ return mtk_snand_do_read_page(snf, addr, buf, oob, raw, true); ++} ++ ++static void mtk_snand_write_fdm(struct mtk_snand *snf, const uint8_t *buf) ++{ ++ uint32_t vall, valm, fdm_size = snf->nfi_soc->fdm_size; ++ const uint8_t *oobptr = buf; ++ int i, j; ++ ++ for (i = 0; i < snf->ecc_steps; i++) { ++ vall = 0; ++ valm = 0; ++ ++ for (j = 0; j < 8; j++) { ++ if (j < 4) ++ vall |= (j < fdm_size ? oobptr[j] : 0xff) ++ << (j * 8); ++ else ++ valm |= (j < fdm_size ? oobptr[j] : 0xff) ++ << ((j - 4) * 8); ++ } ++ ++ nfi_write32(snf, NFI_FDML(i), vall); ++ nfi_write32(snf, NFI_FDMM(i), valm); ++ ++ oobptr += fdm_size; ++ } ++} ++ ++static int mtk_snand_program_load(struct mtk_snand *snf, uint32_t page, ++ bool raw) ++{ ++ uint32_t coladdr, rwbytes, mode, len, val; ++ uintptr_t dma_addr; ++ int ret; ++ ++ /* Column address with plane bit */ ++ coladdr = mtk_snand_get_plane_address(snf, page); ++ ++ mtk_snand_mac_reset(snf); ++ mtk_nfi_reset(snf); ++ ++ /* Write FDM registers if necessary */ ++ if (!raw) ++ mtk_snand_write_fdm(snf, snf->page_cache + snf->writesize); ++ ++ /* Command */ ++ nfi_write32(snf, SNF_PG_CTL1, (snf->opcode_pl << PG_LOAD_CMD_S)); ++ ++ /* Column address */ ++ nfi_write32(snf, SNF_PG_CTL2, coladdr); ++ ++ /* Set write mode */ ++ mode = snf->mode_pl ? PG_LOAD_X4_EN : 0; ++ nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_X4_EN, mode | PG_LOAD_CUSTOM_EN); ++ ++ /* Set bytes to write */ ++ rwbytes = snf->ecc_steps * snf->raw_sector_size; ++ nfi_write32(snf, SNF_MISC_CTL2, (rwbytes << PROGRAM_LOAD_BYTE_NUM_S) | ++ rwbytes); ++ ++ /* NFI write prepare */ ++ mode = raw ? 0 : CNFG_HW_ECC_EN | CNFG_AUTO_FMT_EN; ++ nfi_write16(snf, NFI_CNFG, (CNFG_OP_MODE_PROGRAM << CNFG_OP_MODE_S) | ++ CNFG_DMA_BURST_EN | CNFG_DMA_MODE | mode); ++ ++ nfi_write32(snf, NFI_CON, (snf->ecc_steps << CON_SEC_NUM_S)); ++ ++ /* Prepare for DMA write */ ++ len = snf->writesize + snf->oobsize; ++ ret = dma_mem_map(snf->pdev, snf->page_cache, &dma_addr, len, true); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "DMA map to device failed with %d\n", ret); ++ return ret; ++ } ++ ++ nfi_write32(snf, NFI_STRADDR, (uint32_t)dma_addr); ++ ++ if (!raw) ++ mtk_snand_ecc_encoder_start(snf); ++ ++ /* Prepare for custom write interrupt */ ++ nfi_write32(snf, NFI_INTR_EN, NFI_IRQ_INTR_EN | NFI_IRQ_CUS_PG); ++ irq_completion_init(snf->pdev); ++ ++ /* Trigger NFI into custom mode */ ++ nfi_write16(snf, NFI_CMD, NFI_CMD_DUMMY_WRITE); ++ ++ /* Start DMA write */ ++ nfi_rmw32(snf, NFI_CON, 0, CON_BWR); ++ nfi_write16(snf, NFI_STRDATA, STR_DATA); ++ ++ /* Wait for operation finished */ ++ ret = irq_completion_wait(snf->pdev, snf->nfi_base + SNF_STA_CTL1, ++ CUS_PG_DONE, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "DMA timed out for program load\n"); ++ goto cleanup; ++ } ++ ++ /* Wait for NFI_SEC_CNTR returning expected value */ ++ ret = read32_poll_timeout(snf->nfi_base + NFI_ADDRCNTR, val, ++ NFI_SEC_CNTR(val) >= snf->ecc_steps, ++ 0, SNFI_POLL_INTERVAL); ++ if (ret) { ++ snand_log_nfi(snf->pdev, ++ "Timed out waiting for BUS_SEC_CNTR\n"); ++ goto cleanup; ++ } ++ ++ if (!raw) ++ mtk_snand_ecc_encoder_stop(snf); ++ ++cleanup: ++ /* DMA cleanup */ ++ dma_mem_unmap(snf->pdev, dma_addr, len, true); ++ ++ /* Stop write */ ++ nfi_write32(snf, NFI_CON, 0); ++ nfi_write16(snf, NFI_CNFG, 0); ++ ++ /* Clear SNF done flag */ ++ nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_PG_DONE); ++ nfi_write32(snf, SNF_STA_CTL1, 0); ++ ++ /* Disable interrupt */ ++ nfi_read32(snf, NFI_INTR_STA); ++ nfi_write32(snf, NFI_INTR_EN, 0); ++ ++ nfi_rmw32(snf, SNF_MISC_CTL, PG_LOAD_CUSTOM_EN, 0); ++ ++ return ret; ++} ++ ++static void mtk_snand_to_raw_page(struct mtk_snand *snf, ++ const void *buf, const void *oob, ++ bool empty_ecc) ++{ ++ uint32_t i, ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size; ++ const uint8_t *eccptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size; ++ const uint8_t *bufptr = buf, *oobptr = oob; ++ uint8_t *raw_sector; ++ ++ memset(snf->page_cache, 0xff, snf->writesize + snf->oobsize); ++ for (i = 0; i < snf->ecc_steps; i++) { ++ raw_sector = snf->page_cache + i * snf->raw_sector_size; ++ ++ if (buf) { ++ memcpy(raw_sector, bufptr, snf->nfi_soc->sector_size); ++ bufptr += snf->nfi_soc->sector_size; ++ } ++ ++ raw_sector += snf->nfi_soc->sector_size; ++ ++ if (oob) { ++ memcpy(raw_sector, oobptr, snf->nfi_soc->fdm_size); ++ oobptr += snf->nfi_soc->fdm_size; ++ raw_sector += snf->nfi_soc->fdm_size; ++ ++ if (empty_ecc) ++ memset(raw_sector, 0xff, ecc_bytes); ++ else ++ memcpy(raw_sector, eccptr, ecc_bytes); ++ eccptr += ecc_bytes; ++ } ++ } ++} ++ ++static bool mtk_snand_is_empty_page(struct mtk_snand *snf, const void *buf, ++ const void *oob) ++{ ++ const uint8_t *p = buf; ++ uint32_t i, j; ++ ++ if (buf) { ++ for (i = 0; i < snf->writesize; i++) { ++ if (p[i] != 0xff) ++ return false; ++ } ++ } ++ ++ if (oob) { ++ for (j = 0; j < snf->ecc_steps; j++) { ++ p = oob + j * snf->nfi_soc->fdm_size; ++ ++ for (i = 0; i < snf->nfi_soc->fdm_ecc_size; i++) { ++ if (p[i] != 0xff) ++ return false; ++ } ++ } ++ } ++ ++ return true; ++} ++ ++static int mtk_snand_do_write_page(struct mtk_snand *snf, uint64_t addr, ++ const void *buf, const void *oob, ++ bool raw, bool format) ++{ ++ uint64_t die_addr; ++ bool empty_ecc = false; ++ uint32_t page; ++ int ret; ++ ++ die_addr = mtk_snand_select_die_address(snf, addr); ++ page = die_addr >> snf->writesize_shift; ++ ++ if (!raw && mtk_snand_is_empty_page(snf, buf, oob)) { ++ /* ++ * If the data in the page to be ecc-ed is full 0xff, ++ * change to raw write mode ++ */ ++ raw = true; ++ format = true; ++ ++ /* fill ecc parity code region with 0xff */ ++ empty_ecc = true; ++ } ++ ++ if (raw) { ++ if (format) { ++ mtk_snand_to_raw_page(snf, buf, oob, empty_ecc); ++ mtk_snand_fdm_bm_swap_raw(snf); ++ mtk_snand_bm_swap_raw(snf); ++ } else { ++ memset(snf->page_cache, 0xff, ++ snf->writesize + snf->oobsize); ++ ++ if (buf) ++ memcpy(snf->page_cache, buf, snf->writesize); ++ ++ if (oob) { ++ memcpy(snf->page_cache + snf->writesize, oob, ++ snf->ecc_steps * snf->spare_per_sector); ++ } ++ } ++ } else { ++ memset(snf->page_cache, 0xff, snf->writesize + snf->oobsize); ++ if (buf) ++ memcpy(snf->page_cache, buf, snf->writesize); ++ ++ if (oob) { ++ memcpy(snf->page_cache + snf->writesize, oob, ++ snf->ecc_steps * snf->nfi_soc->fdm_size); ++ } ++ ++ mtk_snand_fdm_bm_swap(snf); ++ mtk_snand_bm_swap(snf); ++ } ++ ++ ret = mtk_snand_write_enable(snf); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_program_load(snf, page, raw); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_page_op(snf, page, SNAND_CMD_PROGRAM_EXECUTE); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL); ++ if (ret < 0) { ++ snand_log_chip(snf->pdev, ++ "Page program command timed out on page %u\n", ++ page); ++ return ret; ++ } ++ ++ if (ret & SNAND_STATUS_PROGRAM_FAIL) { ++ snand_log_chip(snf->pdev, ++ "Page program failed on page %u\n", page); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++int mtk_snand_write_page(struct mtk_snand *snf, uint64_t addr, const void *buf, ++ const void *oob, bool raw) ++{ ++ if (!snf || (!buf && !oob)) ++ return -EINVAL; ++ ++ if (addr >= snf->size) ++ return -EINVAL; ++ ++ return mtk_snand_do_write_page(snf, addr, buf, oob, raw, true); ++} ++ ++int mtk_snand_erase_block(struct mtk_snand *snf, uint64_t addr) ++{ ++ uint64_t die_addr; ++ uint32_t page, block; ++ int ret; ++ ++ if (!snf) ++ return -EINVAL; ++ ++ if (addr >= snf->size) ++ return -EINVAL; ++ ++ die_addr = mtk_snand_select_die_address(snf, addr); ++ block = die_addr >> snf->erasesize_shift; ++ page = block << (snf->erasesize_shift - snf->writesize_shift); ++ ++ ret = mtk_snand_write_enable(snf); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_page_op(snf, page, SNAND_CMD_BLOCK_ERASE); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_poll_status(snf, SNFI_POLL_INTERVAL); ++ if (ret < 0) { ++ snand_log_chip(snf->pdev, ++ "Block erase command timed out on block %u\n", ++ block); ++ return ret; ++ } ++ ++ if (ret & SNAND_STATUS_ERASE_FAIL) { ++ snand_log_chip(snf->pdev, ++ "Block erase failed on block %u\n", block); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int mtk_snand_block_isbad_std(struct mtk_snand *snf, uint64_t addr) ++{ ++ int ret; ++ ++ ret = mtk_snand_do_read_page(snf, addr, NULL, snf->buf_cache, true, ++ false); ++ if (ret && ret != -EBADMSG) ++ return ret; ++ ++ return snf->buf_cache[0] != 0xff; ++} ++ ++static int mtk_snand_block_isbad_mtk(struct mtk_snand *snf, uint64_t addr) ++{ ++ int ret; ++ ++ ret = mtk_snand_do_read_page(snf, addr, NULL, snf->buf_cache, true, ++ true); ++ if (ret && ret != -EBADMSG) ++ return ret; ++ ++ return snf->buf_cache[0] != 0xff; ++} ++ ++int mtk_snand_block_isbad(struct mtk_snand *snf, uint64_t addr) ++{ ++ if (!snf) ++ return -EINVAL; ++ ++ if (addr >= snf->size) ++ return -EINVAL; ++ ++ addr &= ~snf->erasesize_mask; ++ ++ if (snf->nfi_soc->bbm_swap) ++ return mtk_snand_block_isbad_std(snf, addr); ++ ++ return mtk_snand_block_isbad_mtk(snf, addr); ++} ++ ++static int mtk_snand_block_markbad_std(struct mtk_snand *snf, uint64_t addr) ++{ ++ /* Standard BBM position */ ++ memset(snf->buf_cache, 0xff, snf->oobsize); ++ snf->buf_cache[0] = 0; ++ ++ return mtk_snand_do_write_page(snf, addr, NULL, snf->buf_cache, true, ++ false); ++} ++ ++static int mtk_snand_block_markbad_mtk(struct mtk_snand *snf, uint64_t addr) ++{ ++ /* Write the whole page with zeros */ ++ memset(snf->buf_cache, 0, snf->writesize + snf->oobsize); ++ ++ return mtk_snand_do_write_page(snf, addr, snf->buf_cache, ++ snf->buf_cache + snf->writesize, true, ++ true); ++} ++ ++int mtk_snand_block_markbad(struct mtk_snand *snf, uint64_t addr) ++{ ++ if (!snf) ++ return -EINVAL; ++ ++ if (addr >= snf->size) ++ return -EINVAL; ++ ++ addr &= ~snf->erasesize_mask; ++ ++ if (snf->nfi_soc->bbm_swap) ++ return mtk_snand_block_markbad_std(snf, addr); ++ ++ return mtk_snand_block_markbad_mtk(snf, addr); ++} ++ ++int mtk_snand_fill_oob(struct mtk_snand *snf, uint8_t *oobraw, ++ const uint8_t *oobbuf, size_t ooblen) ++{ ++ size_t len = ooblen, sect_fdm_len; ++ const uint8_t *oob = oobbuf; ++ uint32_t step = 0; ++ ++ if (!snf || !oobraw || !oob) ++ return -EINVAL; ++ ++ while (len && step < snf->ecc_steps) { ++ sect_fdm_len = snf->nfi_soc->fdm_size - 1; ++ if (sect_fdm_len > len) ++ sect_fdm_len = len; ++ ++ memcpy(oobraw + step * snf->nfi_soc->fdm_size + 1, oob, ++ sect_fdm_len); ++ ++ len -= sect_fdm_len; ++ oob += sect_fdm_len; ++ step++; ++ } ++ ++ return len; ++} ++ ++int mtk_snand_transfer_oob(struct mtk_snand *snf, uint8_t *oobbuf, ++ size_t ooblen, const uint8_t *oobraw) ++{ ++ size_t len = ooblen, sect_fdm_len; ++ uint8_t *oob = oobbuf; ++ uint32_t step = 0; ++ ++ if (!snf || !oobraw || !oob) ++ return -EINVAL; ++ ++ while (len && step < snf->ecc_steps) { ++ sect_fdm_len = snf->nfi_soc->fdm_size - 1; ++ if (sect_fdm_len > len) ++ sect_fdm_len = len; ++ ++ memcpy(oob, oobraw + step * snf->nfi_soc->fdm_size + 1, ++ sect_fdm_len); ++ ++ len -= sect_fdm_len; ++ oob += sect_fdm_len; ++ step++; ++ } ++ ++ return len; ++} ++ ++int mtk_snand_read_page_auto_oob(struct mtk_snand *snf, uint64_t addr, ++ void *buf, void *oob, size_t ooblen, ++ size_t *actualooblen, bool raw) ++{ ++ int ret, oobremain; ++ ++ if (!snf) ++ return -EINVAL; ++ ++ if (!oob) ++ return mtk_snand_read_page(snf, addr, buf, NULL, raw); ++ ++ ret = mtk_snand_read_page(snf, addr, buf, snf->buf_cache, raw); ++ if (ret && ret != -EBADMSG) { ++ if (actualooblen) ++ *actualooblen = 0; ++ return ret; ++ } ++ ++ oobremain = mtk_snand_transfer_oob(snf, oob, ooblen, snf->buf_cache); ++ if (actualooblen) ++ *actualooblen = ooblen - oobremain; ++ ++ return ret; ++} ++ ++int mtk_snand_write_page_auto_oob(struct mtk_snand *snf, uint64_t addr, ++ const void *buf, const void *oob, ++ size_t ooblen, size_t *actualooblen, bool raw) ++{ ++ int oobremain; ++ ++ if (!snf) ++ return -EINVAL; ++ ++ if (!oob) ++ return mtk_snand_write_page(snf, addr, buf, NULL, raw); ++ ++ memset(snf->buf_cache, 0xff, snf->oobsize); ++ oobremain = mtk_snand_fill_oob(snf, snf->buf_cache, oob, ooblen); ++ if (actualooblen) ++ *actualooblen = ooblen - oobremain; ++ ++ return mtk_snand_write_page(snf, addr, buf, snf->buf_cache, raw); ++} ++ ++int mtk_snand_get_chip_info(struct mtk_snand *snf, ++ struct mtk_snand_chip_info *info) ++{ ++ if (!snf || !info) ++ return -EINVAL; ++ ++ info->model = snf->model; ++ info->chipsize = snf->size; ++ info->blocksize = snf->erasesize; ++ info->pagesize = snf->writesize; ++ info->sparesize = snf->oobsize; ++ info->spare_per_sector = snf->spare_per_sector; ++ info->fdm_size = snf->nfi_soc->fdm_size; ++ info->fdm_ecc_size = snf->nfi_soc->fdm_ecc_size; ++ info->num_sectors = snf->ecc_steps; ++ info->sector_size = snf->nfi_soc->sector_size; ++ info->ecc_strength = snf->ecc_strength; ++ info->ecc_bytes = snf->ecc_bytes; ++ ++ return 0; ++} ++ ++int mtk_snand_irq_process(struct mtk_snand *snf) ++{ ++ uint32_t sta, ien; ++ ++ if (!snf) ++ return -EINVAL; ++ ++ sta = nfi_read32(snf, NFI_INTR_STA); ++ ien = nfi_read32(snf, NFI_INTR_EN); ++ ++ if (!(sta & ien)) ++ return 0; ++ ++ nfi_write32(snf, NFI_INTR_EN, 0); ++ irq_completion_done(snf->pdev); ++ ++ return 1; ++} ++ ++static int mtk_snand_select_spare_per_sector(struct mtk_snand *snf) ++{ ++ uint32_t spare_per_step = snf->oobsize / snf->ecc_steps; ++ int i, mul = 1; ++ ++ /* ++ * If we're using the 1KB sector size, HW will automatically ++ * double the spare size. So we should only use half of the value. ++ */ ++ if (snf->nfi_soc->sector_size == 1024) ++ mul = 2; ++ ++ spare_per_step /= mul; ++ ++ for (i = snf->nfi_soc->num_spare_size - 1; i >= 0; i--) { ++ if (snf->nfi_soc->spare_sizes[i] <= spare_per_step) { ++ snf->spare_per_sector = snf->nfi_soc->spare_sizes[i]; ++ snf->spare_per_sector *= mul; ++ return i; ++ } ++ } ++ ++ snand_log_nfi(snf->pdev, ++ "Page size %u+%u is not supported\n", snf->writesize, ++ snf->oobsize); ++ ++ return -1; ++} ++ ++static int mtk_snand_pagefmt_setup(struct mtk_snand *snf) ++{ ++ uint32_t spare_size_idx, spare_size_shift, pagesize_idx; ++ uint32_t sector_size_512; ++ ++ if (snf->nfi_soc->sector_size == 512) { ++ sector_size_512 = NFI_SEC_SEL_512; ++ spare_size_shift = NFI_SPARE_SIZE_S; ++ } else { ++ sector_size_512 = 0; ++ spare_size_shift = NFI_SPARE_SIZE_LS_S; ++ } ++ ++ switch (snf->writesize) { ++ case SZ_512: ++ pagesize_idx = NFI_PAGE_SIZE_512_2K; ++ break; ++ case SZ_2K: ++ if (snf->nfi_soc->sector_size == 512) ++ pagesize_idx = NFI_PAGE_SIZE_2K_4K; ++ else ++ pagesize_idx = NFI_PAGE_SIZE_512_2K; ++ break; ++ case SZ_4K: ++ if (snf->nfi_soc->sector_size == 512) ++ pagesize_idx = NFI_PAGE_SIZE_4K_8K; ++ else ++ pagesize_idx = NFI_PAGE_SIZE_2K_4K; ++ break; ++ case SZ_8K: ++ if (snf->nfi_soc->sector_size == 512) ++ pagesize_idx = NFI_PAGE_SIZE_8K_16K; ++ else ++ pagesize_idx = NFI_PAGE_SIZE_4K_8K; ++ break; ++ case SZ_16K: ++ pagesize_idx = NFI_PAGE_SIZE_8K_16K; ++ break; ++ default: ++ snand_log_nfi(snf->pdev, "Page size %u is not supported\n", ++ snf->writesize); ++ return -ENOTSUPP; ++ } ++ ++ spare_size_idx = mtk_snand_select_spare_per_sector(snf); ++ if (unlikely(spare_size_idx < 0)) ++ return -ENOTSUPP; ++ ++ snf->raw_sector_size = snf->nfi_soc->sector_size + ++ snf->spare_per_sector; ++ ++ /* Setup page format */ ++ nfi_write32(snf, NFI_PAGEFMT, ++ (snf->nfi_soc->fdm_ecc_size << NFI_FDM_ECC_NUM_S) | ++ (snf->nfi_soc->fdm_size << NFI_FDM_NUM_S) | ++ (spare_size_idx << spare_size_shift) | ++ (pagesize_idx << NFI_PAGE_SIZE_S) | ++ sector_size_512); ++ ++ return 0; ++} ++ ++static enum snand_flash_io mtk_snand_select_opcode(struct mtk_snand *snf, ++ uint32_t snfi_caps, uint8_t *opcode, ++ uint8_t *dummy, ++ const struct snand_io_cap *op_cap) ++{ ++ uint32_t i, caps; ++ ++ caps = snfi_caps & op_cap->caps; ++ ++ i = fls(caps); ++ if (i > 0) { ++ *opcode = op_cap->opcodes[i - 1].opcode; ++ if (dummy) ++ *dummy = op_cap->opcodes[i - 1].dummy; ++ return i - 1; ++ } ++ ++ return __SNAND_IO_MAX; ++} ++ ++static int mtk_snand_select_opcode_rfc(struct mtk_snand *snf, ++ uint32_t snfi_caps, ++ const struct snand_io_cap *op_cap) ++{ ++ enum snand_flash_io idx; ++ ++ static const uint8_t rfc_modes[__SNAND_IO_MAX] = { ++ [SNAND_IO_1_1_1] = DATA_READ_MODE_X1, ++ [SNAND_IO_1_1_2] = DATA_READ_MODE_X2, ++ [SNAND_IO_1_2_2] = DATA_READ_MODE_DUAL, ++ [SNAND_IO_1_1_4] = DATA_READ_MODE_X4, ++ [SNAND_IO_1_4_4] = DATA_READ_MODE_QUAD, ++ }; ++ ++ idx = mtk_snand_select_opcode(snf, snfi_caps, &snf->opcode_rfc, ++ &snf->dummy_rfc, op_cap); ++ if (idx >= __SNAND_IO_MAX) { ++ snand_log_snfi(snf->pdev, ++ "No capable opcode for read from cache\n"); ++ return -ENOTSUPP; ++ } ++ ++ snf->mode_rfc = rfc_modes[idx]; ++ ++ if (idx == SNAND_IO_1_1_4 || idx == SNAND_IO_1_4_4) ++ snf->quad_spi_op = true; ++ ++ return 0; ++} ++ ++static int mtk_snand_select_opcode_pl(struct mtk_snand *snf, uint32_t snfi_caps, ++ const struct snand_io_cap *op_cap) ++{ ++ enum snand_flash_io idx; ++ ++ static const uint8_t pl_modes[__SNAND_IO_MAX] = { ++ [SNAND_IO_1_1_1] = 0, ++ [SNAND_IO_1_1_4] = 1, ++ }; ++ ++ idx = mtk_snand_select_opcode(snf, snfi_caps, &snf->opcode_pl, ++ NULL, op_cap); ++ if (idx >= __SNAND_IO_MAX) { ++ snand_log_snfi(snf->pdev, ++ "No capable opcode for program load\n"); ++ return -ENOTSUPP; ++ } ++ ++ snf->mode_pl = pl_modes[idx]; ++ ++ if (idx == SNAND_IO_1_1_4) ++ snf->quad_spi_op = true; ++ ++ return 0; ++} ++ ++static int mtk_snand_setup(struct mtk_snand *snf, ++ const struct snand_flash_info *snand_info) ++{ ++ const struct snand_mem_org *memorg = &snand_info->memorg; ++ uint32_t i, msg_size, snfi_caps; ++ int ret; ++ ++ /* Calculate flash memory organization */ ++ snf->model = snand_info->model; ++ snf->writesize = memorg->pagesize; ++ snf->oobsize = memorg->sparesize; ++ snf->erasesize = snf->writesize * memorg->pages_per_block; ++ snf->die_size = (uint64_t)snf->erasesize * memorg->blocks_per_die; ++ snf->size = snf->die_size * memorg->ndies; ++ snf->num_dies = memorg->ndies; ++ ++ snf->writesize_mask = snf->writesize - 1; ++ snf->erasesize_mask = snf->erasesize - 1; ++ snf->die_mask = snf->die_size - 1; ++ ++ snf->writesize_shift = ffs(snf->writesize) - 1; ++ snf->erasesize_shift = ffs(snf->erasesize) - 1; ++ snf->die_shift = mtk_snand_ffs64(snf->die_size) - 1; ++ ++ snf->select_die = snand_info->select_die; ++ ++ /* Determine opcodes for read from cache/program load */ ++ snfi_caps = SPI_IO_1_1_1 | SPI_IO_1_1_2 | SPI_IO_1_2_2; ++ if (snf->snfi_quad_spi) ++ snfi_caps |= SPI_IO_1_1_4 | SPI_IO_1_4_4; ++ ++ ret = mtk_snand_select_opcode_rfc(snf, snfi_caps, snand_info->cap_rd); ++ if (ret) ++ return ret; ++ ++ ret = mtk_snand_select_opcode_pl(snf, snfi_caps, snand_info->cap_pl); ++ if (ret) ++ return ret; ++ ++ /* ECC and page format */ ++ snf->ecc_steps = snf->writesize / snf->nfi_soc->sector_size; ++ if (snf->ecc_steps > snf->nfi_soc->max_sectors) { ++ snand_log_nfi(snf->pdev, "Page size %u is not supported\n", ++ snf->writesize); ++ return -ENOTSUPP; ++ } ++ ++ ret = mtk_snand_pagefmt_setup(snf); ++ if (ret) ++ return ret; ++ ++ msg_size = snf->nfi_soc->sector_size + snf->nfi_soc->fdm_ecc_size; ++ ret = mtk_ecc_setup(snf, snf->nfi_base + NFI_FDM0L, ++ snf->spare_per_sector - snf->nfi_soc->fdm_size, ++ msg_size); ++ if (ret) ++ return ret; ++ ++ nfi_write16(snf, NFI_CNFG, 0); ++ ++ /* Tuning options */ ++ nfi_write16(snf, NFI_DEBUG_CON1, WBUF_EN); ++ nfi_write32(snf, SNF_DLY_CTL3, (snf->nfi_soc->sample_delay << SFCK_SAM_DLY_S)); ++ ++ /* Interrupts */ ++ nfi_read32(snf, NFI_INTR_STA); ++ nfi_write32(snf, NFI_INTR_EN, 0); ++ ++ /* Clear SNF done flag */ ++ nfi_rmw32(snf, SNF_STA_CTL1, 0, CUS_READ_DONE | CUS_PG_DONE); ++ nfi_write32(snf, SNF_STA_CTL1, 0); ++ ++ /* Initialization on all dies */ ++ for (i = 0; i < snf->num_dies; i++) { ++ mtk_snand_select_die(snf, i); ++ ++ /* Disable On-Die ECC engine */ ++ ret = mtk_snand_ondie_ecc_control(snf, false); ++ if (ret) ++ return ret; ++ ++ /* Disable block protection */ ++ mtk_snand_unlock(snf); ++ ++ /* Enable/disable quad-spi */ ++ mtk_snand_qspi_control(snf, snf->quad_spi_op); ++ } ++ ++ mtk_snand_select_die(snf, 0); ++ ++ return 0; ++} ++ ++static int mtk_snand_id_probe(struct mtk_snand *snf, ++ const struct snand_flash_info **snand_info) ++{ ++ uint8_t id[4], op[2]; ++ int ret; ++ ++ /* Read SPI-NAND JEDEC ID, OP + dummy/addr + ID */ ++ op[0] = SNAND_CMD_READID; ++ op[1] = 0; ++ ret = mtk_snand_mac_io(snf, op, 2, id, sizeof(id)); ++ if (ret) ++ return ret; ++ ++ *snand_info = snand_flash_id_lookup(SNAND_ID_DYMMY, id); ++ if (*snand_info) ++ return 0; ++ ++ /* Read SPI-NAND JEDEC ID, OP + ID */ ++ op[0] = SNAND_CMD_READID; ++ ret = mtk_snand_mac_io(snf, op, 1, id, sizeof(id)); ++ if (ret) ++ return ret; ++ ++ *snand_info = snand_flash_id_lookup(SNAND_ID_DYMMY, id); ++ if (*snand_info) ++ return 0; ++ ++ snand_log_chip(snf->pdev, ++ "Unrecognized SPI-NAND ID: %02x %02x %02x %02x\n", ++ id[0], id[1], id[2], id[3]); ++ ++ return -EINVAL; ++} ++ ++int mtk_snand_init(void *dev, const struct mtk_snand_platdata *pdata, ++ struct mtk_snand **psnf) ++{ ++ const struct snand_flash_info *snand_info; ++ uint32_t rawpage_size, sect_bf_size; ++ struct mtk_snand tmpsnf, *snf; ++ int ret; ++ ++ if (!pdata || !psnf) ++ return -EINVAL; ++ ++ if (pdata->soc >= __SNAND_SOC_MAX) { ++ snand_log_chip(dev, "Invalid SOC %u for MTK-SNAND\n", ++ pdata->soc); ++ return -EINVAL; ++ } ++ ++ /* Dummy instance only for initial reset and id probe */ ++ tmpsnf.nfi_base = pdata->nfi_base; ++ tmpsnf.ecc_base = pdata->ecc_base; ++ tmpsnf.soc = pdata->soc; ++ tmpsnf.nfi_soc = &mtk_snand_socs[pdata->soc]; ++ tmpsnf.pdev = dev; ++ ++ /* Switch to SNFI mode */ ++ writel(SPI_MODE, tmpsnf.nfi_base + SNF_CFG); ++ ++ /* Reset SNFI & NFI */ ++ mtk_snand_mac_reset(&tmpsnf); ++ mtk_nfi_reset(&tmpsnf); ++ ++ /* Reset SPI-NAND chip */ ++ ret = mtk_snand_chip_reset(&tmpsnf); ++ if (ret) { ++ snand_log_chip(dev, "Failed to reset SPI-NAND chip\n"); ++ return ret; ++ } ++ ++ /* Probe SPI-NAND flash by JEDEC ID */ ++ ret = mtk_snand_id_probe(&tmpsnf, &snand_info); ++ if (ret) ++ return ret; ++ ++ rawpage_size = snand_info->memorg.pagesize + ++ snand_info->memorg.sparesize; ++ ++ sect_bf_size = mtk_snand_socs[pdata->soc].max_sectors * ++ sizeof(*snf->sect_bf); ++ ++ /* Allocate memory for instance and cache */ ++ snf = generic_mem_alloc(dev, ++ sizeof(*snf) + rawpage_size + sect_bf_size); ++ if (!snf) { ++ snand_log_chip(dev, "Failed to allocate memory for instance\n"); ++ return -ENOMEM; ++ } ++ ++ snf->sect_bf = (int *)((uintptr_t)snf + sizeof(*snf)); ++ snf->buf_cache = (uint8_t *)((uintptr_t)snf->sect_bf + sect_bf_size); ++ ++ /* Allocate memory for DMA buffer */ ++ snf->page_cache = dma_mem_alloc(dev, rawpage_size); ++ if (!snf->page_cache) { ++ generic_mem_free(dev, snf); ++ snand_log_chip(dev, ++ "Failed to allocate memory for DMA buffer\n"); ++ return -ENOMEM; ++ } ++ ++ /* Fill up instance */ ++ snf->pdev = dev; ++ snf->nfi_base = pdata->nfi_base; ++ snf->ecc_base = pdata->ecc_base; ++ snf->soc = pdata->soc; ++ snf->nfi_soc = &mtk_snand_socs[pdata->soc]; ++ snf->snfi_quad_spi = pdata->quad_spi; ++ ++ /* Initialize SNFI & ECC engine */ ++ ret = mtk_snand_setup(snf, snand_info); ++ if (ret) { ++ dma_mem_free(dev, snf->page_cache); ++ generic_mem_free(dev, snf); ++ return ret; ++ } ++ ++ *psnf = snf; ++ ++ return 0; ++} ++ ++int mtk_snand_cleanup(struct mtk_snand *snf) ++{ ++ if (!snf) ++ return 0; ++ ++ dma_mem_free(snf->pdev, snf->page_cache); ++ generic_mem_free(snf->pdev, snf); ++ ++ return 0; ++} +--- a/drivers/mtd/mtk-snand/mtk-snand.h ++++ b/drivers/mtd/mtk-snand/mtk-snand.h +@@ -0,0 +1,77 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _MTK_SNAND_H_ ++#define _MTK_SNAND_H_ ++ ++#ifndef PRIVATE_MTK_SNAND_HEADER ++#include ++#include ++#include ++#endif ++ ++enum mtk_snand_soc { ++ SNAND_SOC_MT7622, ++ SNAND_SOC_MT7629, ++ SNAND_SOC_MT7981, ++ SNAND_SOC_MT7986, ++ __SNAND_SOC_MAX ++}; ++ ++struct mtk_snand_platdata { ++ void *nfi_base; ++ void *ecc_base; ++ enum mtk_snand_soc soc; ++ bool quad_spi; ++}; ++ ++struct mtk_snand_chip_info { ++ const char *model; ++ uint64_t chipsize; ++ uint32_t blocksize; ++ uint32_t pagesize; ++ uint32_t sparesize; ++ uint32_t spare_per_sector; ++ uint32_t fdm_size; ++ uint32_t fdm_ecc_size; ++ uint32_t num_sectors; ++ uint32_t sector_size; ++ uint32_t ecc_strength; ++ uint32_t ecc_bytes; ++}; ++ ++struct mtk_snand; ++struct snand_flash_info; ++ ++int mtk_snand_init(void *dev, const struct mtk_snand_platdata *pdata, ++ struct mtk_snand **psnf); ++int mtk_snand_cleanup(struct mtk_snand *snf); ++ ++int mtk_snand_chip_reset(struct mtk_snand *snf); ++int mtk_snand_read_page(struct mtk_snand *snf, uint64_t addr, void *buf, ++ void *oob, bool raw); ++int mtk_snand_write_page(struct mtk_snand *snf, uint64_t addr, const void *buf, ++ const void *oob, bool raw); ++int mtk_snand_erase_block(struct mtk_snand *snf, uint64_t addr); ++int mtk_snand_block_isbad(struct mtk_snand *snf, uint64_t addr); ++int mtk_snand_block_markbad(struct mtk_snand *snf, uint64_t addr); ++int mtk_snand_fill_oob(struct mtk_snand *snf, uint8_t *oobraw, ++ const uint8_t *oobbuf, size_t ooblen); ++int mtk_snand_transfer_oob(struct mtk_snand *snf, uint8_t *oobbuf, ++ size_t ooblen, const uint8_t *oobraw); ++int mtk_snand_read_page_auto_oob(struct mtk_snand *snf, uint64_t addr, ++ void *buf, void *oob, size_t ooblen, ++ size_t *actualooblen, bool raw); ++int mtk_snand_write_page_auto_oob(struct mtk_snand *snf, uint64_t addr, ++ const void *buf, const void *oob, ++ size_t ooblen, size_t *actualooblen, ++ bool raw); ++int mtk_snand_get_chip_info(struct mtk_snand *snf, ++ struct mtk_snand_chip_info *info); ++int mtk_snand_irq_process(struct mtk_snand *snf); ++ ++#endif /* _MTK_SNAND_H_ */ diff --git a/patch/u-boot/u-boot-filogic/100-03-mtd-mtk-snand-add-support-for-SPL.patch b/patch/u-boot/u-boot-filogic/100-03-mtd-mtk-snand-add-support-for-SPL.patch new file mode 100644 index 0000000000..27b56f7bd3 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-03-mtd-mtk-snand-add-support-for-SPL.patch @@ -0,0 +1,175 @@ +From a347e374cb338213632c6dde88dd226d64bd8b27 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Wed, 3 Mar 2021 08:57:29 +0800 +Subject: [PATCH 37/71] mtd: mtk-snand: add support for SPL + +Add support to initialize SPI-NAND in SPL. +Add implementation for SPL NAND loader. + +Signed-off-by: Weijie Gao +--- + drivers/mtd/mtk-snand/Kconfig | 6 ++ + drivers/mtd/mtk-snand/Makefile | 4 + + drivers/mtd/mtk-snand/mtk-snand-spl.c | 133 ++++++++++++++++++++++++++ + 3 files changed, 143 insertions(+) + create mode 100644 drivers/mtd/mtk-snand/mtk-snand-spl.c + +--- a/drivers/mtd/mtk-snand/Kconfig ++++ b/drivers/mtd/mtk-snand/Kconfig +@@ -19,3 +19,9 @@ config MTK_SPI_NAND_MTD + help + This option enables access to SPI-NAND flashes through the + MTD interface of MediaTek SPI NAND Flash Controller ++ ++config SPL_MTK_SPI_NAND ++ tristate "SPL support for MediaTek SPI NAND flash controller" ++ depends on MTK_SPI_NAND ++ help ++ This option enables access to SPI-NAND flashes in the SPL stage +--- a/drivers/mtd/mtk-snand/Makefile ++++ b/drivers/mtd/mtk-snand/Makefile +@@ -8,4 +8,8 @@ + obj-y += mtk-snand.o mtk-snand-ecc.o mtk-snand-ids.o mtk-snand-os.o + obj-$(CONFIG_MTK_SPI_NAND_MTD) += mtk-snand-mtd.o + ++ifdef CONFIG_SPL_BUILD ++obj-$(CONFIG_SPL_MTK_SPI_NAND) += mtk-snand-spl.o ++endif ++ + ccflags-y += -DPRIVATE_MTK_SNAND_HEADER +--- /dev/null ++++ b/drivers/mtd/mtk-snand/mtk-snand-spl.c +@@ -0,0 +1,133 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mtk-snand.h" ++ ++static struct mtk_snand *snf; ++static struct mtk_snand_chip_info cinfo; ++static u32 oobavail; ++ ++static u8 *page_cache; ++ ++int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst) ++{ ++ u32 sizeremain = size, chunksize, leading; ++ uint32_t off = offs, writesize_mask = cinfo.pagesize - 1; ++ uint8_t *ptr = dst; ++ int ret; ++ ++ if (!snf) ++ return -ENODEV; ++ ++ while (sizeremain) { ++ schedule(); ++ ++ leading = off & writesize_mask; ++ chunksize = cinfo.pagesize - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ if (chunksize == cinfo.pagesize) { ++ ret = mtk_snand_read_page(snf, off - leading, ptr, ++ NULL, false); ++ if (ret) ++ break; ++ } else { ++ ret = mtk_snand_read_page(snf, off - leading, ++ page_cache, NULL, false); ++ if (ret) ++ break; ++ ++ memcpy(ptr, page_cache + leading, chunksize); ++ } ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ return ret; ++} ++ ++void nand_init(void) ++{ ++ struct mtk_snand_platdata mtk_snand_pdata = {}; ++ struct udevice *dev; ++ fdt_addr_t base; ++ int ret; ++ ++ ret = uclass_get_device_by_driver(UCLASS_MTD, DM_DRIVER_GET(mtk_snand), ++ &dev); ++ if (ret) { ++ printf("mtk-snand-spl: Device instance not found!\n"); ++ return; ++ } ++ ++ base = dev_read_addr_name(dev, "nfi"); ++ if (base == FDT_ADDR_T_NONE) { ++ printf("mtk-snand-spl: NFI base not set\n"); ++ return; ++ } ++ mtk_snand_pdata.nfi_base = map_sysmem(base, 0); ++ ++ base = dev_read_addr_name(dev, "ecc"); ++ if (base == FDT_ADDR_T_NONE) { ++ printf("mtk-snand-spl: ECC base not set\n"); ++ return; ++ } ++ mtk_snand_pdata.ecc_base = map_sysmem(base, 0); ++ ++ mtk_snand_pdata.soc = dev_get_driver_data(dev); ++ mtk_snand_pdata.quad_spi = dev_read_bool(dev, "quad-spi"); ++ ++ ret = mtk_snand_init(NULL, &mtk_snand_pdata, &snf); ++ if (ret) { ++ printf("mtk-snand-spl: failed to initialize SPI-NAND\n"); ++ return; ++ } ++ ++ mtk_snand_get_chip_info(snf, &cinfo); ++ ++ oobavail = cinfo.num_sectors * (cinfo.fdm_size - 1); ++ ++ printf("SPI-NAND: %s (%uMB)\n", cinfo.model, ++ (u32)(cinfo.chipsize >> 20)); ++ ++ page_cache = malloc(cinfo.pagesize + cinfo.sparesize); ++ if (!page_cache) { ++ mtk_snand_cleanup(snf); ++ printf("mtk-snand-spl: failed to allocate page cache\n"); ++ } ++} ++ ++void nand_deselect(void) ++{ ++ ++} ++ ++static const struct udevice_id mtk_snand_ids[] = { ++ { .compatible = "mediatek,mt7622-snand", .data = SNAND_SOC_MT7622 }, ++ { .compatible = "mediatek,mt7629-snand", .data = SNAND_SOC_MT7629 }, ++ { .compatible = "mediatek,mt7981-snand", .data = SNAND_SOC_MT7981 }, ++ { .compatible = "mediatek,mt7986-snand", .data = SNAND_SOC_MT7986 }, ++ { /* sentinel */ }, ++}; ++ ++U_BOOT_DRIVER(mtk_snand) = { ++ .name = "mtk-snand", ++ .id = UCLASS_MTD, ++ .of_match = mtk_snand_ids, ++ .flags = DM_FLAG_PRE_RELOC, ++}; diff --git a/patch/u-boot/u-boot-filogic/100-04-env-add-support-for-generic-MTD-device.patch b/patch/u-boot/u-boot-filogic/100-04-env-add-support-for-generic-MTD-device.patch new file mode 100644 index 0000000000..e0edda8d70 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-04-env-add-support-for-generic-MTD-device.patch @@ -0,0 +1,390 @@ +From efc3e6f5d29f87a433b42f15a0b87e04b7cd498d Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Wed, 3 Mar 2021 10:11:32 +0800 +Subject: [PATCH 38/71] env: add support for generic MTD device + +Add an env driver for generic MTD device. + +Signed-off-by: Weijie Gao +--- + cmd/nvedit.c | 3 +- + env/Kconfig | 37 +++++- + env/Makefile | 1 + + env/env.c | 3 + + env/mtd.c | 256 +++++++++++++++++++++++++++++++++++++++++ + include/env_internal.h | 1 + + tools/Makefile | 1 + + 7 files changed, 299 insertions(+), 3 deletions(-) + create mode 100644 env/mtd.c + +--- a/env/Kconfig ++++ b/env/Kconfig +@@ -74,7 +74,7 @@ config ENV_IS_DEFAULT + !ENV_IS_IN_MMC && !ENV_IS_IN_NAND && \ + !ENV_IS_IN_NVRAM && !ENV_IS_IN_ONENAND && \ + !ENV_IS_IN_REMOTE && !ENV_IS_IN_SPI_FLASH && \ +- !ENV_IS_IN_UBI ++ !ENV_IS_IN_UBI && !ENV_IS_IN_MTD + select ENV_IS_NOWHERE + + config ENV_IS_NOWHERE +@@ -267,6 +267,27 @@ config ENV_IS_IN_MMC + offset: "u-boot,mmc-env-offset", "u-boot,mmc-env-offset-redundant". + CONFIG_ENV_OFFSET and CONFIG_ENV_OFFSET_REDUND are not used. + ++config ENV_IS_IN_MTD ++ bool "Environment in a MTD device" ++ depends on !CHAIN_OF_TRUST ++ depends on MTD ++ help ++ Define this if you have a MTD device which you want to use for ++ the environment. ++ ++ - CONFIG_ENV_MTD_NAME: ++ - CONFIG_ENV_OFFSET: ++ - CONFIG_ENV_SIZE: ++ ++ These three #defines specify the MTD device where the environment ++ is stored, offset and size of the environment area within the MTD ++ device. CONFIG_ENV_OFFSET must be aligned to an erase block boundary. ++ ++ - CONFIG_ENV_SIZE_REDUND: ++ ++ This #define specify the maximum size allowed for read/write/erase ++ with skipped bad blocks starting from ENV_OFFSET. ++ + config ENV_IS_IN_NAND + bool "Environment in a NAND device" + depends on !CHAIN_OF_TRUST +@@ -574,10 +595,16 @@ config ENV_ADDR_REDUND + Offset from the start of the device (or partition) of the redundant + environment location. + ++config ENV_MTD_NAME ++ string "Name of the MTD device storing the environment" ++ depends on ENV_IS_IN_MTD ++ help ++ Name of the MTD device that stores the environment ++ + config ENV_OFFSET + hex "Environment offset" + depends on ENV_IS_IN_EEPROM || ENV_IS_IN_MMC || ENV_IS_IN_NAND || \ +- ENV_IS_IN_SPI_FLASH ++ ENV_IS_IN_SPI_FLASH || ENV_IS_IN_MTD + default 0x3f8000 if ARCH_ROCKCHIP && ENV_IS_IN_MMC + default 0x140000 if ARCH_ROCKCHIP && ENV_IS_IN_SPI_FLASH + default 0xF0000 if ARCH_SUNXI +@@ -635,6 +662,12 @@ config ENV_SECT_SIZE + help + Size of the sector containing the environment. + ++config ENV_SIZE_REDUND ++ hex "Redundant environment size" ++ depends on ENV_IS_IN_MTD ++ help ++ The maximum size allowed for read/write/erase with skipped bad blocks. ++ + config ENV_UBI_PART + string "UBI partition name" + depends on ENV_IS_IN_UBI +--- a/env/Makefile ++++ b/env/Makefile +@@ -24,6 +24,7 @@ obj-$(CONFIG_$(PHASE_)ENV_IS_NOWHERE) += + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_MMC) += mmc.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_FAT) += fat.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_EXT4) += ext4.o ++obj-$(CONFIG_$(PHASE_)ENV_IS_IN_MTD) += mtd.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_NAND) += nand.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_SPI_FLASH) += sf.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_FLASH) += flash.o +--- a/env/env.c ++++ b/env/env.c +@@ -46,6 +46,9 @@ static enum env_location env_locations[] + #ifdef CONFIG_ENV_IS_IN_MMC + ENVL_MMC, + #endif ++#ifdef CONFIG_ENV_IS_IN_MTD ++ ENVL_MTD, ++#endif + #ifdef CONFIG_ENV_IS_IN_NAND + ENVL_NAND, + #endif +--- /dev/null ++++ b/env/mtd.c +@@ -0,0 +1,256 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#if CONFIG_ENV_SIZE_REDUND < CONFIG_ENV_SIZE ++#undef CONFIG_ENV_SIZE_REDUND ++#define CONFIG_ENV_SIZE_REDUND CONFIG_ENV_SIZE ++#endif ++ ++#if defined(ENV_IS_EMBEDDED) ++env_t *env_ptr = &environment; ++#else /* ! ENV_IS_EMBEDDED */ ++env_t *env_ptr; ++#endif /* ENV_IS_EMBEDDED */ ++ ++DECLARE_GLOBAL_DATA_PTR; ++ ++static int env_mtd_init(void) ++{ ++#if defined(ENV_IS_EMBEDDED) ++ int crc1_ok = 0, crc2_ok = 0; ++ env_t *tmp_env1; ++ ++ tmp_env1 = env_ptr; ++ crc1_ok = crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc; ++ ++ if (!crc1_ok && !crc2_ok) { ++ gd->env_addr = 0; ++ gd->env_valid = ENV_INVALID; ++ ++ return 0; ++ } else if (crc1_ok && !crc2_ok) { ++ gd->env_valid = ENV_VALID; ++ } ++ ++ if (gd->env_valid == ENV_VALID) ++ env_ptr = tmp_env1; ++ ++ gd->env_addr = (ulong)env_ptr->data; ++ ++#else /* ENV_IS_EMBEDDED */ ++ gd->env_addr = (ulong)&default_environment[0]; ++ gd->env_valid = ENV_VALID; ++#endif /* ENV_IS_EMBEDDED */ ++ ++ return 0; ++} ++ ++static struct mtd_info *env_mtd_get_dev(void) ++{ ++ struct mtd_info *mtd; ++ ++ mtd_probe_devices(); ++ ++ mtd = get_mtd_device_nm(CONFIG_ENV_MTD_NAME); ++ if (IS_ERR(mtd) || !mtd) { ++ printf("MTD device '%s' not found\n", CONFIG_ENV_MTD_NAME); ++ return NULL; ++ } ++ ++ return mtd; ++} ++ ++static inline bool mtd_addr_is_block_aligned(struct mtd_info *mtd, u64 addr) ++{ ++ return (addr & mtd->erasesize_mask) == 0; ++} ++ ++static int mtd_io_skip_bad(struct mtd_info *mtd, bool read, loff_t offset, ++ size_t length, size_t redund, u8 *buffer) ++{ ++ struct mtd_oob_ops io_op = {}; ++ size_t remaining = length; ++ loff_t off, end; ++ int ret; ++ ++ io_op.mode = MTD_OPS_PLACE_OOB; ++ io_op.len = mtd->writesize; ++ io_op.datbuf = (void *)buffer; ++ ++ /* Search for the first good block after the given offset */ ++ off = offset; ++ end = (off + redund) | (mtd->erasesize - 1); ++ while (mtd_block_isbad(mtd, off) && off < end) ++ off += mtd->erasesize; ++ ++ /* Reached end position */ ++ if (off >= end) ++ return -EIO; ++ ++ /* Loop over the pages to do the actual read/write */ ++ while (remaining) { ++ /* Skip the block if it is bad */ ++ if (mtd_addr_is_block_aligned(mtd, off) && ++ mtd_block_isbad(mtd, off)) { ++ off += mtd->erasesize; ++ continue; ++ } ++ ++ if (read) ++ ret = mtd_read_oob(mtd, off, &io_op); ++ else ++ ret = mtd_write_oob(mtd, off, &io_op); ++ ++ if (ret) { ++ printf("Failure while %s at offset 0x%llx\n", ++ read ? "reading" : "writing", off); ++ break; ++ } ++ ++ off += io_op.retlen; ++ remaining -= io_op.retlen; ++ io_op.datbuf += io_op.retlen; ++ io_op.oobbuf += io_op.oobretlen; ++ ++ /* Reached end position */ ++ if (off >= end) ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++#ifdef CONFIG_CMD_SAVEENV ++static int mtd_erase_skip_bad(struct mtd_info *mtd, loff_t offset, ++ size_t length, size_t redund) ++{ ++ struct erase_info erase_op = {}; ++ loff_t end = (offset + redund) | (mtd->erasesize - 1); ++ int ret; ++ ++ erase_op.mtd = mtd; ++ erase_op.addr = offset; ++ erase_op.len = length; ++ ++ while (erase_op.len) { ++ ret = mtd_erase(mtd, &erase_op); ++ ++ /* Abort if its not a bad block error */ ++ if (ret != -EIO) ++ return ret; ++ ++ printf("Skipping bad block at 0x%08llx\n", erase_op.fail_addr); ++ ++ /* Skip bad block and continue behind it */ ++ erase_op.len -= erase_op.fail_addr - erase_op.addr; ++ erase_op.len -= mtd->erasesize; ++ erase_op.addr = erase_op.fail_addr + mtd->erasesize; ++ ++ /* Reached end position */ ++ if (erase_op.addr >= end) ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int env_mtd_save(void) ++{ ++ ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1); ++ struct mtd_info *mtd; ++ int ret = 0; ++ ++ ret = env_export(env_new); ++ if (ret) ++ return ret; ++ ++ mtd = env_mtd_get_dev(); ++ if (!mtd) ++ return 1; ++ ++ printf("Erasing on MTD device '%s'... ", mtd->name); ++ ++ ret = mtd_erase_skip_bad(mtd, CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, ++ CONFIG_ENV_SIZE_REDUND); ++ ++ puts(ret ? "FAILED\n" : "OK\n"); ++ ++ if (ret) { ++ put_mtd_device(mtd); ++ return 1; ++ } ++ ++ printf("Writing to MTD device '%s'... ", mtd->name); ++ ++ ret = mtd_io_skip_bad(mtd, false, CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, ++ CONFIG_ENV_SIZE_REDUND, (u8 *)env_new); ++ ++ puts(ret ? "FAILED\n" : "OK\n"); ++ ++ put_mtd_device(mtd); ++ ++ return !!ret; ++} ++#endif /* CONFIG_CMD_SAVEENV */ ++ ++static int readenv(size_t offset, u_char *buf) ++{ ++ struct mtd_info *mtd; ++ int ret; ++ ++ mtd = env_mtd_get_dev(); ++ if (!mtd) ++ return 1; ++ ++ ret = mtd_io_skip_bad(mtd, true, offset, CONFIG_ENV_SIZE, ++ CONFIG_ENV_SIZE_REDUND, buf); ++ ++ put_mtd_device(mtd); ++ ++ return !!ret; ++} ++ ++static int env_mtd_load(void) ++{ ++#if !defined(ENV_IS_EMBEDDED) ++ ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE); ++ int ret; ++ ++ ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf); ++ if (ret) { ++ env_set_default("readenv() failed", 0); ++ return -EIO; ++ } ++ ++ return env_import(buf, 1, H_EXTERNAL); ++#endif /* ! ENV_IS_EMBEDDED */ ++ ++ return 0; ++} ++ ++U_BOOT_ENV_LOCATION(mtd) = { ++ .location = ENVL_MTD, ++ ENV_NAME("MTD") ++ .load = env_mtd_load, ++#if defined(CONFIG_CMD_SAVEENV) ++ .save = env_save_ptr(env_mtd_save), ++#endif ++ .init = env_mtd_init, ++}; +--- a/include/env_internal.h ++++ b/include/env_internal.h +@@ -108,6 +108,7 @@ enum env_location { + ENVL_FAT, + ENVL_FLASH, + ENVL_MMC, ++ ENVL_MTD, + ENVL_NAND, + ENVL_NVRAM, + ENVL_ONENAND, +--- a/tools/Makefile ++++ b/tools/Makefile +@@ -37,6 +37,7 @@ subdir-$(HOST_TOOLS_ALL) += gdb + ENVCRC-$(CONFIG_ENV_IS_IN_EEPROM) = y + ENVCRC-$(CONFIG_ENV_IS_IN_FLASH) = y + ENVCRC-$(CONFIG_ENV_IS_IN_ONENAND) = y ++ENVCRC-$(CONFIG_ENV_IS_IN_MTD) = y + ENVCRC-$(CONFIG_ENV_IS_IN_NAND) = y + ENVCRC-$(CONFIG_ENV_IS_IN_NVRAM) = y + ENVCRC-$(CONFIG_ENV_IS_IN_SPI_FLASH) = y diff --git a/patch/u-boot/u-boot-filogic/100-05-mtd-add-a-new-mtd-device-type-for-NMBM.patch b/patch/u-boot/u-boot-filogic/100-05-mtd-add-a-new-mtd-device-type-for-NMBM.patch new file mode 100644 index 0000000000..aa19a11587 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-05-mtd-add-a-new-mtd-device-type-for-NMBM.patch @@ -0,0 +1,44 @@ +From d26a789c451068caf4bbb4d1ac7bc1f592b5493e Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:58:06 +0800 +Subject: [PATCH 39/71] mtd: add a new mtd device type for NMBM + +This patch adds a new mtd device type for NMBM so that mtdparts can be +correctly probed. And this also gives us an opportunity to add NMBM support +for filesystems in the future. + +Signed-off-by: Weijie Gao +--- + cmd/mtdparts.c | 3 +++ + include/jffs2/load_kernel.h | 4 +++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +--- a/cmd/mtdparts.c ++++ b/cmd/mtdparts.c +@@ -1054,6 +1054,9 @@ int mtd_id_parse(const char *id, const c + } else if (strncmp(p, "spi-nand", 8) == 0) { + *dev_type = MTD_DEV_TYPE_SPINAND; + p += 8; ++ } else if (strncmp(p, "nmbm", 4) == 0) { ++ *dev_type = MTD_DEV_TYPE_NMBM; ++ p += 4; + } else { + printf("incorrect device type in %s\n", id); + return 1; +--- a/include/jffs2/load_kernel.h ++++ b/include/jffs2/load_kernel.h +@@ -17,11 +17,13 @@ + #define MTD_DEV_TYPE_NAND 0x0002 + #define MTD_DEV_TYPE_ONENAND 0x0004 + #define MTD_DEV_TYPE_SPINAND 0x0008 ++#define MTD_DEV_TYPE_NMBM 0x0010 + + #define MTD_DEV_TYPE(type) (type == MTD_DEV_TYPE_NAND ? "nand" : \ + (type == MTD_DEV_TYPE_NOR ? "nor" : \ + (type == MTD_DEV_TYPE_ONENAND ? "onenand" : \ +- "spi-nand"))) \ ++ (type == MTD_DEV_TYPE_SPINAND ? "spi-nand" : \ ++ "nmbm")))) \ + + struct mtd_device { + struct list_head link; diff --git a/patch/u-boot/u-boot-filogic/100-06-mtd-add-core-facility-code-of-NMBM.patch b/patch/u-boot/u-boot-filogic/100-06-mtd-add-core-facility-code-of-NMBM.patch new file mode 100644 index 0000000000..eb35d87041 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-06-mtd-add-core-facility-code-of-NMBM.patch @@ -0,0 +1,3533 @@ +From 690479081fb6a0c0f77f10fb457ad69e71390f15 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:26:35 +0800 +Subject: [PATCH 40/71] mtd: add core facility code of NMBM + +This patch adds a NAND bad block management named NMBM (NAND mapping block +management) which supports using a mapping table to deal with bad blocks +before factory shipping and during use. + +Signed-off-by: Weijie Gao +--- + drivers/mtd/Kconfig | 2 + + drivers/mtd/Makefile | 1 + + drivers/mtd/nmbm/Kconfig | 29 + + drivers/mtd/nmbm/Makefile | 5 + + drivers/mtd/nmbm/nmbm-core.c | 3040 +++++++++++++++++++++++++++++++ + drivers/mtd/nmbm/nmbm-debug.h | 37 + + drivers/mtd/nmbm/nmbm-debug.inl | 39 + + drivers/mtd/nmbm/nmbm-private.h | 137 ++ + include/nmbm/nmbm-os.h | 68 + + include/nmbm/nmbm.h | 105 ++ + 10 files changed, 3463 insertions(+) + create mode 100644 drivers/mtd/nmbm/Kconfig + create mode 100644 drivers/mtd/nmbm/Makefile + create mode 100644 drivers/mtd/nmbm/nmbm-core.c + create mode 100644 drivers/mtd/nmbm/nmbm-debug.h + create mode 100644 drivers/mtd/nmbm/nmbm-debug.inl + create mode 100644 drivers/mtd/nmbm/nmbm-private.h + create mode 100644 include/nmbm/nmbm-os.h + create mode 100644 include/nmbm/nmbm.h + +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -276,6 +276,8 @@ config SYS_NAND_MAX_CHIPS + help + The maximum number of NAND chips per device to be supported. + ++source "drivers/mtd/nmbm/Kconfig" ++ + source "drivers/mtd/spi/Kconfig" + + source "drivers/mtd/ubi/Kconfig" +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -42,3 +42,4 @@ obj-$(CONFIG_SPL_UBI) += ubispl/ + endif + + obj-$(CONFIG_MTK_SPI_NAND) += mtk-snand/ ++obj-$(CONFIG_NMBM) += nmbm/ +--- a/drivers/mtd/nmbm/Kconfig ++++ b/drivers/mtd/nmbm/Kconfig +@@ -0,0 +1,29 @@ ++ ++config NMBM ++ bool "Enable NAND mapping block management" ++ default n ++ ++choice ++ prompt "Default log level" ++ depends on NMBM ++ default NMBM_LOG_LEVEL_INFO ++ ++config NMBM_LOG_LEVEL_DEBUG ++ bool "0 - Debug" ++ ++config NMBM_LOG_LEVEL_INFO ++ bool "1 - Info" ++ ++config NMBM_LOG_LEVEL_WARN ++ bool "2 - Warn" ++ ++config NMBM_LOG_LEVEL_ERR ++ bool "3 - Error" ++ ++config NMBM_LOG_LEVEL_EMERG ++ bool "4 - Emergency" ++ ++config NMBM_LOG_LEVEL_NONE ++ bool "5 - None" ++ ++endchoice +--- a/drivers/mtd/nmbm/Makefile ++++ b/drivers/mtd/nmbm/Makefile +@@ -0,0 +1,5 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# ++# (C) Copyright 2020 MediaTek Inc. All rights reserved. ++ ++obj-$(CONFIG_NMBM) += nmbm-core.o +--- a/drivers/mtd/nmbm/nmbm-core.c ++++ b/drivers/mtd/nmbm/nmbm-core.c +@@ -0,0 +1,3040 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* ++ * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include "nmbm-private.h" ++ ++#include "nmbm-debug.h" ++ ++#define NMBM_VER_MAJOR 1 ++#define NMBM_VER_MINOR 0 ++#define NMBM_VER NMBM_VERSION_MAKE(NMBM_VER_MAJOR, \ ++ NMBM_VER_MINOR) ++ ++#define NMBM_ALIGN(v, a) (((v) + (a) - 1) & ~((a) - 1)) ++ ++/*****************************************************************************/ ++/* Logging related functions */ ++/*****************************************************************************/ ++ ++/* ++ * nmbm_log_lower - Print log using OS specific routine ++ * @nld: NMBM lower device structure ++ * @level: log level ++ * @fmt: format string ++ */ ++static void nmbm_log_lower(struct nmbm_lower_device *nld, ++ enum nmbm_log_category level, const char *fmt, ...) ++{ ++ va_list ap; ++ ++ if (!nld->logprint) ++ return; ++ ++ va_start(ap, fmt); ++ nld->logprint(nld->arg, level, fmt, ap); ++ va_end(ap); ++} ++ ++/* ++ * nmbm_log - Print log using OS specific routine ++ * @ni: NMBM instance structure ++ * @level: log level ++ * @fmt: format string ++ */ ++static void nmbm_log(struct nmbm_instance *ni, enum nmbm_log_category level, ++ const char *fmt, ...) ++{ ++ va_list ap; ++ ++ if (!ni) ++ return; ++ ++ if (!ni->lower.logprint || level < ni->log_display_level) ++ return; ++ ++ va_start(ap, fmt); ++ ni->lower.logprint(ni->lower.arg, level, fmt, ap); ++ va_end(ap); ++} ++ ++/* ++ * nmbm_set_log_level - Set log display level ++ * @ni: NMBM instance structure ++ * @level: log display level ++ */ ++enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, ++ enum nmbm_log_category level) ++{ ++ enum nmbm_log_category old; ++ ++ if (!ni) ++ return __NMBM_LOG_MAX; ++ ++ old = ni->log_display_level; ++ ni->log_display_level = level; ++ return old; ++} ++ ++/* ++ * nlog_table_creation - Print log of table creation event ++ * @ni: NMBM instance structure ++ * @main_table: whether the table is main info table ++ * @start_ba: start block address of the table ++ * @end_ba: block address after the end of the table ++ */ ++static void nlog_table_creation(struct nmbm_instance *ni, bool main_table, ++ uint32_t start_ba, uint32_t end_ba) ++{ ++ if (start_ba == end_ba - 1) ++ nlog_info(ni, "%s info table has been written to block %u\n", ++ main_table ? "Main" : "Backup", start_ba); ++ else ++ nlog_info(ni, "%s info table has been written to block %u-%u\n", ++ main_table ? "Main" : "Backup", start_ba, end_ba - 1); ++ ++ nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); ++} ++ ++/* ++ * nlog_table_update - Print log of table update event ++ * @ni: NMBM instance structure ++ * @main_table: whether the table is main info table ++ * @start_ba: start block address of the table ++ * @end_ba: block address after the end of the table ++ */ ++static void nlog_table_update(struct nmbm_instance *ni, bool main_table, ++ uint32_t start_ba, uint32_t end_ba) ++{ ++ if (start_ba == end_ba - 1) ++ nlog_debug(ni, "%s info table has been updated in block %u\n", ++ main_table ? "Main" : "Backup", start_ba); ++ else ++ nlog_debug(ni, "%s info table has been updated in block %u-%u\n", ++ main_table ? "Main" : "Backup", start_ba, end_ba - 1); ++ ++ nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); ++} ++ ++/* ++ * nlog_table_found - Print log of table found event ++ * @ni: NMBM instance structure ++ * @first_table: whether the table is first found info table ++ * @write_count: write count of the info table ++ * @start_ba: start block address of the table ++ * @end_ba: block address after the end of the table ++ */ ++static void nlog_table_found(struct nmbm_instance *ni, bool first_table, ++ uint32_t write_count, uint32_t start_ba, ++ uint32_t end_ba) ++{ ++ if (start_ba == end_ba - 1) ++ nlog_info(ni, "%s info table with writecount %u found in block %u\n", ++ first_table ? "First" : "Second", write_count, ++ start_ba); ++ else ++ nlog_info(ni, "%s info table with writecount %u found in block %u-%u\n", ++ first_table ? "First" : "Second", write_count, ++ start_ba, end_ba - 1); ++ ++ nmbm_mark_block_color_info_table(ni, start_ba, end_ba - 1); ++} ++ ++/*****************************************************************************/ ++/* Address conversion functions */ ++/*****************************************************************************/ ++ ++/* ++ * addr2ba - Convert a linear address to block address ++ * @ni: NMBM instance structure ++ * @addr: Linear address ++ */ ++static uint32_t addr2ba(struct nmbm_instance *ni, uint64_t addr) ++{ ++ return addr >> ni->erasesize_shift; ++} ++ ++/* ++ * ba2addr - Convert a block address to linear address ++ * @ni: NMBM instance structure ++ * @ba: Block address ++ */ ++static uint64_t ba2addr(struct nmbm_instance *ni, uint32_t ba) ++{ ++ return (uint64_t)ba << ni->erasesize_shift; ++} ++/* ++ * size2blk - Get minimum required blocks for storing specific size of data ++ * @ni: NMBM instance structure ++ * @size: size for storing ++ */ ++static uint32_t size2blk(struct nmbm_instance *ni, uint64_t size) ++{ ++ return (size + ni->lower.erasesize - 1) >> ni->erasesize_shift; ++} ++ ++/*****************************************************************************/ ++/* High level NAND chip APIs */ ++/*****************************************************************************/ ++ ++/* ++ * nmbm_reset_chip - Reset NAND device ++ * @nld: Lower NAND chip structure ++ */ ++static void nmbm_reset_chip(struct nmbm_instance *ni) ++{ ++ if (ni->lower.reset_chip) ++ ni->lower.reset_chip(ni->lower.arg); ++} ++ ++/* ++ * nmbm_read_phys_page - Read page with retry ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be read from ++ * @data: the main data to be read ++ * @oob: the oob data to be read ++ * @mode: mode for processing oob data ++ * ++ * Read a page for at most NMBM_TRY_COUNT times. ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++static int nmbm_read_phys_page(struct nmbm_instance *ni, uint64_t addr, ++ void *data, void *oob, enum nmbm_oob_mode mode) ++{ ++ int tries, ret; ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ ret = ni->lower.read_page(ni->lower.arg, addr, data, oob, mode); ++ if (ret >= 0) ++ return ret; ++ ++ nmbm_reset_chip(ni); ++ } ++ ++ if (ret != -EBADMSG) ++ nlog_err(ni, "Page read failed at address 0x%08llx\n", addr); ++ ++ return ret; ++} ++ ++/* ++ * nmbm_write_phys_page - Write page with retry ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be written to ++ * @data: the main data to be written ++ * @oob: the oob data to be written ++ * @mode: mode for processing oob data ++ * ++ * Write a page for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_write_phys_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, const void *oob, ++ enum nmbm_oob_mode mode) ++{ ++ int tries, ret; ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) { ++ nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); ++ return false; ++ } ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ ret = ni->lower.write_page(ni->lower.arg, addr, data, oob, mode); ++ if (!ret) ++ return true; ++ ++ nmbm_reset_chip(ni); ++ } ++ ++ nlog_err(ni, "Page write failed at address 0x%08llx\n", addr); ++ ++ return false; ++} ++ ++/* ++ * nmbm_panic_write_phys_page - Panic write page with retry ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be written to ++ * @data: the main data to be written ++ * ++ * Write a page for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_panic_write_phys_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data) ++{ ++ int tries, ret; ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) { ++ nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); ++ return false; ++ } ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ ret = ni->lower.panic_write_page(ni->lower.arg, addr, data); ++ if (!ret) ++ return true; ++ ++ nmbm_reset_chip(ni); ++ } ++ ++ nlog_err(ni, "Panic page write failed at address 0x%08llx\n", addr); ++ ++ return false; ++} ++ ++/* ++ * nmbm_erase_phys_block - Erase a block with retry ++ * @ni: NMBM instance structure ++ * @addr: Linear address ++ * ++ * Erase a block for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_erase_phys_block(struct nmbm_instance *ni, uint64_t addr) ++{ ++ int tries, ret; ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) { ++ nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); ++ return false; ++ } ++ ++ for (tries = 0; tries < NMBM_TRY_COUNT; tries++) { ++ ret = ni->lower.erase_block(ni->lower.arg, addr); ++ if (!ret) ++ return true; ++ ++ nmbm_reset_chip(ni); ++ } ++ ++ nlog_err(ni, "Block erasure failed at address 0x%08llx\n", addr); ++ ++ return false; ++} ++ ++/* ++ * nmbm_check_bad_phys_block - Check whether a block is marked bad in OOB ++ * @ni: NMBM instance structure ++ * @ba: block address ++ */ ++static bool nmbm_check_bad_phys_block(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint64_t addr = ba2addr(ni, ba); ++ int ret; ++ ++ if (ni->lower.is_bad_block) ++ return ni->lower.is_bad_block(ni->lower.arg, addr); ++ ++ /* Treat ECC error as read success */ ++ ret = nmbm_read_phys_page(ni, addr, NULL, ++ ni->page_cache + ni->lower.writesize, ++ NMBM_MODE_RAW); ++ if (ret < 0 && ret != -EBADMSG) ++ return true; ++ ++ return ni->page_cache[ni->lower.writesize] != 0xff; ++} ++ ++/* ++ * nmbm_mark_phys_bad_block - Mark a block bad ++ * @ni: NMBM instance structure ++ * @addr: Linear address ++ */ ++static int nmbm_mark_phys_bad_block(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint64_t addr = ba2addr(ni, ba); ++ enum nmbm_log_category level; ++ uint32_t off; ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) { ++ nlog_err(ni, "%s called with NMBM_F_READ_ONLY set\n", addr); ++ return false; ++ } ++ ++ nlog_info(ni, "Block %u [0x%08llx] will be marked bad\n", ba, addr); ++ ++ if (ni->lower.mark_bad_block) ++ return ni->lower.mark_bad_block(ni->lower.arg, addr); ++ ++ /* Whole page set to 0x00 */ ++ memset(ni->page_cache, 0, ni->rawpage_size); ++ ++ /* Write to all pages within this block, disable all errors */ ++ level = nmbm_set_log_level(ni, __NMBM_LOG_MAX); ++ ++ for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { ++ nmbm_write_phys_page(ni, addr + off, ni->page_cache, ++ ni->page_cache + ni->lower.writesize, ++ NMBM_MODE_RAW); ++ } ++ ++ nmbm_set_log_level(ni, level); ++ ++ return 0; ++} ++ ++/*****************************************************************************/ ++/* NMBM related functions */ ++/*****************************************************************************/ ++ ++/* ++ * nmbm_check_header - Check whether a NMBM structure is valid ++ * @data: pointer to a NMBM structure with a NMBM header at beginning ++ * @size: Size of the buffer pointed by @header ++ * ++ * The size of the NMBM structure may be larger than NMBM header, ++ * e.g. block mapping table and block state table. ++ */ ++static bool nmbm_check_header(const void *data, uint32_t size) ++{ ++ const struct nmbm_header *header = data; ++ struct nmbm_header nhdr; ++ uint32_t new_checksum; ++ ++ /* ++ * Make sure expected structure size is equal or smaller than ++ * buffer size. ++ */ ++ if (header->size > size) ++ return false; ++ ++ memcpy(&nhdr, data, sizeof(nhdr)); ++ ++ nhdr.checksum = 0; ++ new_checksum = nmbm_crc32(0, &nhdr, sizeof(nhdr)); ++ if (header->size > sizeof(nhdr)) ++ new_checksum = nmbm_crc32(new_checksum, ++ (const uint8_t *)data + sizeof(nhdr), ++ header->size - sizeof(nhdr)); ++ ++ if (header->checksum != new_checksum) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * nmbm_update_checksum - Update checksum of a NMBM structure ++ * @header: pointer to a NMBM structure with a NMBM header at beginning ++ * ++ * The size of the NMBM structure must be specified by @header->size ++ */ ++static void nmbm_update_checksum(struct nmbm_header *header) ++{ ++ header->checksum = 0; ++ header->checksum = nmbm_crc32(0, header, header->size); ++} ++ ++/* ++ * nmbm_get_spare_block_count - Calculate number of blocks should be reserved ++ * @block_count: number of blocks of data ++ * ++ * Calculate number of blocks should be reserved for data ++ */ ++static uint32_t nmbm_get_spare_block_count(uint32_t block_count) ++{ ++ uint32_t val; ++ ++ val = (block_count + NMBM_SPARE_BLOCK_DIV / 2) / NMBM_SPARE_BLOCK_DIV; ++ val *= NMBM_SPARE_BLOCK_MULTI; ++ ++ if (val < NMBM_SPARE_BLOCK_MIN) ++ val = NMBM_SPARE_BLOCK_MIN; ++ ++ return val; ++} ++ ++/* ++ * nmbm_get_block_state_raw - Get state of a block from raw block state table ++ * @block_state: pointer to raw block state table (bitmap) ++ * @ba: block address ++ */ ++static uint32_t nmbm_get_block_state_raw(nmbm_bitmap_t *block_state, ++ uint32_t ba) ++{ ++ uint32_t unit, shift; ++ ++ unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; ++ shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; ++ ++ return (block_state[unit] >> shift) & BLOCK_ST_MASK; ++} ++ ++/* ++ * nmbm_get_block_state - Get state of a block from block state table ++ * @ni: NMBM instance structure ++ * @ba: block address ++ */ ++static uint32_t nmbm_get_block_state(struct nmbm_instance *ni, uint32_t ba) ++{ ++ return nmbm_get_block_state_raw(ni->block_state, ba); ++} ++ ++/* ++ * nmbm_set_block_state - Set state of a block to block state table ++ * @ni: NMBM instance structure ++ * @ba: block address ++ * @state: block state ++ * ++ * Set state of a block. If the block state changed, ni->block_state_changed ++ * will be increased. ++ */ ++static bool nmbm_set_block_state(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t state) ++{ ++ uint32_t unit, shift, orig; ++ nmbm_bitmap_t uv; ++ ++ unit = ba / NMBM_BITMAP_BLOCKS_PER_UNIT; ++ shift = (ba % NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_BITS_PER_BLOCK; ++ ++ orig = (ni->block_state[unit] >> shift) & BLOCK_ST_MASK; ++ state &= BLOCK_ST_MASK; ++ ++ uv = ni->block_state[unit] & (~(BLOCK_ST_MASK << shift)); ++ uv |= state << shift; ++ ni->block_state[unit] = uv; ++ ++ if (state == BLOCK_ST_BAD) ++ nmbm_mark_block_color_bad(ni, ba); ++ ++ if (orig != state) { ++ ni->block_state_changed++; ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_block_walk_asc - Skip specified number of good blocks, ascending addr. ++ * @ni: NMBM instance structure ++ * @ba: start physical block address ++ * @nba: return physical block address after walk ++ * @count: number of good blocks to be skipped ++ * @limit: highest block address allowed for walking ++ * ++ * Start from @ba, skipping any bad blocks, counting @count good blocks, and ++ * return the next good block address. ++ * ++ * If no enough good blocks counted while @limit reached, false will be returned. ++ * ++ * If @count == 0, nearest good block address will be returned. ++ * @limit is not counted in walking. ++ */ ++static bool nmbm_block_walk_asc(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *nba, uint32_t count, ++ uint32_t limit) ++{ ++ int32_t nblock = count; ++ ++ if (limit >= ni->block_count) ++ limit = ni->block_count - 1; ++ ++ while (ba < limit) { ++ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) ++ nblock--; ++ ++ if (nblock < 0) { ++ *nba = ba; ++ return true; ++ } ++ ++ ba++; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_block_walk_desc - Skip specified number of good blocks, descending addr ++ * @ni: NMBM instance structure ++ * @ba: start physical block address ++ * @nba: return physical block address after walk ++ * @count: number of good blocks to be skipped ++ * @limit: lowest block address allowed for walking ++ * ++ * Start from @ba, skipping any bad blocks, counting @count good blocks, and ++ * return the next good block address. ++ * ++ * If no enough good blocks counted while @limit reached, false will be returned. ++ * ++ * If @count == 0, nearest good block address will be returned. ++ * @limit is not counted in walking. ++ */ ++static bool nmbm_block_walk_desc(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *nba, uint32_t count, uint32_t limit) ++{ ++ int32_t nblock = count; ++ ++ if (limit >= ni->block_count) ++ limit = ni->block_count - 1; ++ ++ while (ba > limit) { ++ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_GOOD) ++ nblock--; ++ ++ if (nblock < 0) { ++ *nba = ba; ++ return true; ++ } ++ ++ ba--; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_block_walk - Skip specified number of good blocks from curr. block addr ++ * @ni: NMBM instance structure ++ * @ascending: whether to walk ascending ++ * @ba: start physical block address ++ * @nba: return physical block address after walk ++ * @count: number of good blocks to be skipped ++ * @limit: highest/lowest block address allowed for walking ++ * ++ * Start from @ba, skipping any bad blocks, counting @count good blocks, and ++ * return the next good block address. ++ * ++ * If no enough good blocks counted while @limit reached, false will be returned. ++ * ++ * If @count == 0, nearest good block address will be returned. ++ * @limit can be set to negative if no limit required. ++ * @limit is not counted in walking. ++ */ ++static bool nmbm_block_walk(struct nmbm_instance *ni, bool ascending, ++ uint32_t ba, uint32_t *nba, int32_t count, ++ int32_t limit) ++{ ++ if (ascending) ++ return nmbm_block_walk_asc(ni, ba, nba, count, limit); ++ ++ return nmbm_block_walk_desc(ni, ba, nba, count, limit); ++} ++ ++/* ++ * nmbm_scan_badblocks - Scan and record all bad blocks ++ * @ni: NMBM instance structure ++ * ++ * Scan the entire lower NAND chip and record all bad blocks in to block state ++ * table. ++ */ ++static void nmbm_scan_badblocks(struct nmbm_instance *ni) ++{ ++ uint32_t ba; ++ ++ for (ba = 0; ba < ni->block_count; ba++) { ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ nlog_info(ni, "Bad block %u [0x%08llx]\n", ba, ++ ba2addr(ni, ba)); ++ } ++ } ++} ++ ++/* ++ * nmbm_build_mapping_table - Build initial block mapping table ++ * @ni: NMBM instance structure ++ * ++ * The initial mapping table will be compatible with the stratage of ++ * factory production. ++ */ ++static void nmbm_build_mapping_table(struct nmbm_instance *ni) ++{ ++ uint32_t pb, lb; ++ ++ for (pb = 0, lb = 0; pb < ni->mgmt_start_ba; pb++) { ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) ++ continue; ++ ++ /* Always map to the next good block */ ++ ni->block_mapping[lb++] = pb; ++ } ++ ++ ni->data_block_count = lb; ++ ++ /* Unusable/Management blocks */ ++ for (pb = lb; pb < ni->block_count; pb++) ++ ni->block_mapping[pb] = -1; ++} ++ ++/* ++ * nmbm_erase_block_and_check - Erase a block and check its usability ++ * @ni: NMBM instance structure ++ * @ba: block address to be erased ++ * ++ * Erase a block anc check its usability ++ * ++ * Return true if the block is usable, false if erasure failure or the block ++ * has too many bitflips. ++ */ ++static bool nmbm_erase_block_and_check(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint64_t addr, off; ++ bool success; ++ int ret; ++ ++ success = nmbm_erase_phys_block(ni, ba2addr(ni, ba)); ++ if (!success) ++ return false; ++ ++ if (!(ni->lower.flags & NMBM_F_EMPTY_PAGE_ECC_OK)) ++ return true; ++ ++ /* Check every page to make sure there aren't too many bitflips */ ++ ++ addr = ba2addr(ni, ba); ++ ++ for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { ++ WATCHDOG_RESET(); ++ ++ ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, ++ NMBM_MODE_PLACE_OOB); ++ if (ret == -EBADMSG) { ++ /* ++ * NMBM_F_EMPTY_PAGE_ECC_OK means the empty page is ++ * still protected by ECC. So reading pages with ECC ++ * enabled and -EBADMSG means there are too many ++ * bitflips that can't be recovered, and the block ++ * containing the page should be marked bad. ++ */ ++ nlog_err(ni, ++ "Too many bitflips in empty page at 0x%llx\n", ++ addr + off); ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_erase_range - Erase a range of blocks ++ * @ni: NMBM instance structure ++ * @ba: block address where the erasure will start ++ * @limit: top block address allowed for erasure ++ * ++ * Erase blocks within the specific range. Newly-found bad blocks will be ++ * marked. ++ * ++ * @limit is not counted into the allowed erasure address. ++ */ ++static void nmbm_erase_range(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit) ++{ ++ bool success; ++ ++ while (ba < limit) { ++ WATCHDOG_RESET(); ++ ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, ba); ++ if (success) ++ goto next_block; ++ ++ nmbm_mark_phys_bad_block(ni, ba); ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba++; ++ } ++} ++ ++/* ++ * nmbm_write_repeated_data - Write critical data to a block with retry ++ * @ni: NMBM instance structure ++ * @ba: block address where the data will be written to ++ * @data: the data to be written ++ * @size: size of the data ++ * ++ * Write data to every page of the block. Success only if all pages within ++ * this block have been successfully written. ++ * ++ * Make sure data size is not bigger than one page. ++ * ++ * This function will write and verify every page for at most ++ * NMBM_TRY_COUNT times. ++ */ ++static bool nmbm_write_repeated_data(struct nmbm_instance *ni, uint32_t ba, ++ const void *data, uint32_t size) ++{ ++ uint64_t addr, off; ++ bool success; ++ int ret; ++ ++ if (size > ni->lower.writesize) ++ return false; ++ ++ addr = ba2addr(ni, ba); ++ ++ for (off = 0; off < ni->lower.erasesize; off += ni->lower.writesize) { ++ WATCHDOG_RESET(); ++ ++ /* Prepare page data. fill 0xff to unused region */ ++ memcpy(ni->page_cache, data, size); ++ memset(ni->page_cache + size, 0xff, ni->rawpage_size - size); ++ ++ success = nmbm_write_phys_page(ni, addr + off, ni->page_cache, ++ NULL, NMBM_MODE_PLACE_OOB); ++ if (!success) ++ return false; ++ ++ /* Verify the data just written. ECC error indicates failure */ ++ ret = nmbm_read_phys_page(ni, addr + off, ni->page_cache, NULL, ++ NMBM_MODE_PLACE_OOB); ++ if (ret < 0) ++ return false; ++ ++ if (memcmp(ni->page_cache, data, size)) ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_write_signature - Write signature to NAND chip ++ * @ni: NMBM instance structure ++ * @limit: top block address allowed for writing ++ * @signature: the signature to be written ++ * @signature_ba: the actual block address where signature is written to ++ * ++ * Write signature within a specific range, from chip bottom to limit. ++ * At most one block will be written. ++ * ++ * @limit is not counted into the allowed write address. ++ */ ++static bool nmbm_write_signature(struct nmbm_instance *ni, uint32_t limit, ++ const struct nmbm_signature *signature, ++ uint32_t *signature_ba) ++{ ++ uint32_t ba = ni->block_count - 1; ++ bool success; ++ ++ while (ba > limit) { ++ WATCHDOG_RESET(); ++ ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, ba); ++ if (!success) ++ goto skip_bad_block; ++ ++ success = nmbm_write_repeated_data(ni, ba, signature, ++ sizeof(*signature)); ++ if (success) { ++ *signature_ba = ba; ++ return true; ++ } ++ ++ skip_bad_block: ++ nmbm_mark_phys_bad_block(ni, ba); ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba--; ++ }; ++ ++ return false; ++} ++ ++/* ++ * nmbn_read_data - Read data ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be read from ++ * @data: the data to be read ++ * @size: the size of data ++ * ++ * Read data range. ++ * Every page will be tried for at most NMBM_TRY_COUNT times. ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++static int nmbn_read_data(struct nmbm_instance *ni, uint64_t addr, void *data, ++ uint32_t size) ++{ ++ uint64_t off = addr; ++ uint8_t *ptr = data; ++ uint32_t sizeremain = size, chunksize, leading; ++ int ret; ++ ++ while (sizeremain) { ++ WATCHDOG_RESET(); ++ ++ leading = off & ni->writesize_mask; ++ chunksize = ni->lower.writesize - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ if (chunksize == ni->lower.writesize) { ++ ret = nmbm_read_phys_page(ni, off - leading, ptr, NULL, ++ NMBM_MODE_PLACE_OOB); ++ if (ret < 0) ++ return ret; ++ } else { ++ ret = nmbm_read_phys_page(ni, off - leading, ++ ni->page_cache, NULL, ++ NMBM_MODE_PLACE_OOB); ++ if (ret < 0) ++ return ret; ++ ++ memcpy(ptr, ni->page_cache + leading, chunksize); ++ } ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ return 0; ++} ++ ++/* ++ * nmbn_write_verify_data - Write data with validation ++ * @ni: NMBM instance structure ++ * @addr: linear address where the data will be written to ++ * @data: the data to be written ++ * @size: the size of data ++ * ++ * Write data and verify. ++ * Every page will be tried for at most NMBM_TRY_COUNT times. ++ */ ++static bool nmbn_write_verify_data(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, uint32_t size) ++{ ++ uint64_t off = addr; ++ const uint8_t *ptr = data; ++ uint32_t sizeremain = size, chunksize, leading; ++ bool success; ++ int ret; ++ ++ while (sizeremain) { ++ WATCHDOG_RESET(); ++ ++ leading = off & ni->writesize_mask; ++ chunksize = ni->lower.writesize - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ /* Prepare page data. fill 0xff to unused region */ ++ memset(ni->page_cache, 0xff, ni->rawpage_size); ++ memcpy(ni->page_cache + leading, ptr, chunksize); ++ ++ success = nmbm_write_phys_page(ni, off - leading, ++ ni->page_cache, NULL, ++ NMBM_MODE_PLACE_OOB); ++ if (!success) ++ return false; ++ ++ /* Verify the data just written. ECC error indicates failure */ ++ ret = nmbm_read_phys_page(ni, off - leading, ni->page_cache, ++ NULL, NMBM_MODE_PLACE_OOB); ++ if (ret < 0) ++ return false; ++ ++ if (memcmp(ni->page_cache + leading, ptr, chunksize)) ++ return false; ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_write_mgmt_range - Write management data into NAND within a range ++ * @ni: NMBM instance structure ++ * @addr: preferred start block address for writing ++ * @limit: highest block address allowed for writing ++ * @data: the data to be written ++ * @size: the size of data ++ * @actual_start_ba: actual start block address of data ++ * @actual_end_ba: block address after the end of data ++ * ++ * @limit is not counted into the allowed write address. ++ */ ++static bool nmbm_write_mgmt_range(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit, const void *data, ++ uint32_t size, uint32_t *actual_start_ba, ++ uint32_t *actual_end_ba) ++{ ++ const uint8_t *ptr = data; ++ uint32_t sizeremain = size, chunksize; ++ bool success; ++ ++ while (sizeremain && ba < limit) { ++ WATCHDOG_RESET(); ++ ++ chunksize = sizeremain; ++ if (chunksize > ni->lower.erasesize) ++ chunksize = ni->lower.erasesize; ++ ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, ba); ++ if (!success) ++ goto skip_bad_block; ++ ++ success = nmbn_write_verify_data(ni, ba2addr(ni, ba), ptr, ++ chunksize); ++ if (!success) ++ goto skip_bad_block; ++ ++ if (sizeremain == size) ++ *actual_start_ba = ba; ++ ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ ++ goto next_block; ++ ++ skip_bad_block: ++ nmbm_mark_phys_bad_block(ni, ba); ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba++; ++ } ++ ++ if (sizeremain) ++ return false; ++ ++ *actual_end_ba = ba; ++ ++ return true; ++} ++ ++/* ++ * nmbm_generate_info_table_cache - Generate info table cache data ++ * @ni: NMBM instance structure ++ * ++ * Generate info table cache data to be written into flash. ++ */ ++static bool nmbm_generate_info_table_cache(struct nmbm_instance *ni) ++{ ++ bool changed = false; ++ ++ memset(ni->info_table_cache, 0xff, ni->info_table_size); ++ ++ memcpy(ni->info_table_cache + ni->info_table.state_table_off, ++ ni->block_state, ni->state_table_size); ++ ++ memcpy(ni->info_table_cache + ni->info_table.mapping_table_off, ++ ni->block_mapping, ni->mapping_table_size); ++ ++ ni->info_table.header.magic = NMBM_MAGIC_INFO_TABLE; ++ ni->info_table.header.version = NMBM_VER; ++ ni->info_table.header.size = ni->info_table_size; ++ ++ if (ni->block_state_changed || ni->block_mapping_changed) { ++ ni->info_table.write_count++; ++ changed = true; ++ } ++ ++ memcpy(ni->info_table_cache, &ni->info_table, sizeof(ni->info_table)); ++ ++ nmbm_update_checksum((struct nmbm_header *)ni->info_table_cache); ++ ++ return changed; ++} ++ ++/* ++ * nmbm_write_info_table - Write info table into NAND within a range ++ * @ni: NMBM instance structure ++ * @ba: preferred start block address for writing ++ * @limit: highest block address allowed for writing ++ * @actual_start_ba: actual start block address of info table ++ * @actual_end_ba: block address after the end of info table ++ * ++ * @limit is counted into the allowed write address. ++ */ ++static bool nmbm_write_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit, uint32_t *actual_start_ba, ++ uint32_t *actual_end_ba) ++{ ++ return nmbm_write_mgmt_range(ni, ba, limit, ni->info_table_cache, ++ ni->info_table_size, actual_start_ba, ++ actual_end_ba); ++} ++ ++/* ++ * nmbm_mark_tables_clean - Mark info table `clean' ++ * @ni: NMBM instance structure ++ */ ++static void nmbm_mark_tables_clean(struct nmbm_instance *ni) ++{ ++ ni->block_state_changed = 0; ++ ni->block_mapping_changed = 0; ++} ++ ++/* ++ * nmbm_try_reserve_blocks - Reserve blocks with compromisation ++ * @ni: NMBM instance structure ++ * @ba: start physical block address ++ * @nba: return physical block address after reservation ++ * @count: number of good blocks to be skipped ++ * @min_count: minimum number of good blocks to be skipped ++ * @limit: highest/lowest block address allowed for walking ++ * ++ * Reserve specific blocks. If failed, try to reserve as many as possible. ++ */ ++static bool nmbm_try_reserve_blocks(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *nba, uint32_t count, ++ int32_t min_count, int32_t limit) ++{ ++ int32_t nblocks = count; ++ bool success; ++ ++ while (nblocks >= min_count) { ++ success = nmbm_block_walk(ni, true, ba, nba, nblocks, limit); ++ if (success) ++ return true; ++ ++ nblocks--; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_rebuild_info_table - Build main & backup info table from scratch ++ * @ni: NMBM instance structure ++ * @allow_no_gap: allow no spare blocks between two tables ++ */ ++static bool nmbm_rebuild_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t table_start_ba, table_end_ba, next_start_ba; ++ uint32_t main_table_end_ba; ++ bool success; ++ ++ /* Set initial value */ ++ ni->main_table_ba = 0; ++ ni->backup_table_ba = 0; ++ ni->mapping_blocks_ba = ni->mapping_blocks_top_ba; ++ ++ /* Write main table */ ++ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, &table_end_ba); ++ if (!success) { ++ /* Failed to write main table, data will be lost */ ++ nlog_emerg(ni, "Unable to write at least one info table!\n"); ++ nlog_emerg(ni, "Please save your data before power off!\n"); ++ ni->protected = 1; ++ return false; ++ } ++ ++ /* Main info table is successfully written, record its offset */ ++ ni->main_table_ba = table_start_ba; ++ main_table_end_ba = table_end_ba; ++ ++ /* Adjust mapping_blocks_ba */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_creation(ni, true, table_start_ba, table_end_ba); ++ ++ /* Reserve spare blocks for main info table. */ ++ success = nmbm_try_reserve_blocks(ni, table_end_ba, ++ &next_start_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ size2blk(ni, ni->info_table_size)); ++ if (!success) { ++ /* There is no spare block. */ ++ nlog_debug(ni, "No room for backup info table\n"); ++ return true; ++ } ++ ++ /* Write backup info table. */ ++ success = nmbm_write_info_table(ni, next_start_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, &table_end_ba); ++ if (!success) { ++ /* There is no enough blocks for backup table. */ ++ nlog_debug(ni, "No room for backup info table\n"); ++ return true; ++ } ++ ++ /* Backup table is successfully written, record its offset */ ++ ni->backup_table_ba = table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); ++ ++ nlog_table_creation(ni, false, table_start_ba, table_end_ba); ++ ++ return true; ++} ++ ++/* ++ * nmbm_rescue_single_info_table - Rescue when there is only one info table ++ * @ni: NMBM instance structure ++ * ++ * This function is called when there is only one info table exists. ++ * This function may fail if we can't write new info table ++ */ ++static bool nmbm_rescue_single_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t table_start_ba, table_end_ba, write_ba; ++ bool success; ++ ++ /* Try to write new info table in front of existing table */ ++ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, ++ ni->main_table_ba, ++ &table_start_ba, ++ &table_end_ba); ++ if (success) { ++ /* ++ * New table becomes the main table, existing table becomes ++ * the backup table. ++ */ ++ ni->backup_table_ba = ni->main_table_ba; ++ ni->main_table_ba = table_start_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, table_end_ba, ni->backup_table_ba); ++ ++ nlog_table_creation(ni, true, table_start_ba, table_end_ba); ++ ++ return true; ++ } ++ ++ /* Try to reserve spare blocks for existing table */ ++ success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ size2blk(ni, ni->info_table_size)); ++ if (!success) { ++ nlog_warn(ni, "Failed to rescue single info table\n"); ++ return false; ++ } ++ ++ /* Try to write new info table next to the existing table */ ++ while (write_ba >= ni->mapping_blocks_ba) { ++ WATCHDOG_RESET(); ++ ++ success = nmbm_write_info_table(ni, write_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, ++ &table_end_ba); ++ if (success) ++ break; ++ ++ write_ba--; ++ } ++ ++ if (success) { ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, ni->mapping_blocks_ba, table_start_ba); ++ ++ /* New table becomes the backup table */ ++ ni->backup_table_ba = table_start_ba; ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_creation(ni, false, table_start_ba, table_end_ba); ++ ++ return true; ++ } ++ ++ nlog_warn(ni, "Failed to rescue single info table\n"); ++ return false; ++} ++ ++/* ++ * nmbm_update_single_info_table - Update specific one info table ++ * @ni: NMBM instance structure ++ */ ++static bool nmbm_update_single_info_table(struct nmbm_instance *ni, ++ bool update_main_table) ++{ ++ uint32_t write_start_ba, write_limit, table_start_ba, table_end_ba; ++ bool success; ++ ++ /* Determine the write range */ ++ if (update_main_table) { ++ write_start_ba = ni->main_table_ba; ++ write_limit = ni->backup_table_ba; ++ } else { ++ write_start_ba = ni->backup_table_ba; ++ write_limit = ni->mapping_blocks_top_ba; ++ } ++ ++ nmbm_mark_block_color_mgmt(ni, write_start_ba, write_limit - 1); ++ ++ success = nmbm_write_info_table(ni, write_start_ba, write_limit, ++ &table_start_ba, &table_end_ba); ++ if (success) { ++ if (update_main_table) { ++ ni->main_table_ba = table_start_ba; ++ } else { ++ ni->backup_table_ba = table_start_ba; ++ ni->mapping_blocks_ba = table_end_ba; ++ } ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_update(ni, update_main_table, table_start_ba, ++ table_end_ba); ++ ++ return true; ++ } ++ ++ if (update_main_table) { ++ /* ++ * If failed to update main table, make backup table the new ++ * main table, and call nmbm_rescue_single_info_table() ++ */ ++ nlog_warn(ni, "Unable to update %s info table\n", ++ update_main_table ? "Main" : "Backup"); ++ ++ ni->main_table_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ return nmbm_rescue_single_info_table(ni); ++ } ++ ++ /* Only one table left */ ++ ni->mapping_blocks_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ ++ return false; ++} ++ ++/* ++ * nmbm_rescue_main_info_table - Rescue when failed to write main info table ++ * @ni: NMBM instance structure ++ * ++ * This function is called when main info table failed to be written, and ++ * backup info table exists. ++ */ ++static bool nmbm_rescue_main_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t tmp_table_start_ba, tmp_table_end_ba, main_table_start_ba; ++ uint32_t main_table_end_ba, write_ba; ++ uint32_t info_table_erasesize = size2blk(ni, ni->info_table_size); ++ bool success; ++ ++ /* Try to reserve spare blocks for existing backup info table */ ++ success = nmbm_try_reserve_blocks(ni, ni->mapping_blocks_ba, &write_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ info_table_erasesize); ++ if (!success) { ++ /* There is no spare block. Backup info table becomes the main table. */ ++ nlog_err(ni, "No room for temporary info table\n"); ++ ni->main_table_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Try to write temporary info table into spare unmapped blocks */ ++ while (write_ba >= ni->mapping_blocks_ba) { ++ WATCHDOG_RESET(); ++ ++ success = nmbm_write_info_table(ni, write_ba, ++ ni->mapping_blocks_top_ba, ++ &tmp_table_start_ba, ++ &tmp_table_end_ba); ++ if (success) ++ break; ++ ++ write_ba--; ++ } ++ ++ if (!success) { ++ /* Backup info table becomes the main table */ ++ nlog_err(ni, "Failed to update main info table\n"); ++ ni->main_table_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = tmp_table_end_ba; ++ ++ nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, ++ tmp_table_end_ba - 1); ++ ++ /* ++ * Now write main info table at the beginning of management area. ++ * This operation will generally destroy the original backup info ++ * table. ++ */ ++ success = nmbm_write_info_table(ni, ni->mgmt_start_ba, ++ tmp_table_start_ba, ++ &main_table_start_ba, ++ &main_table_end_ba); ++ if (!success) { ++ /* Temporary info table becomes the main table */ ++ ni->main_table_ba = tmp_table_start_ba; ++ ni->backup_table_ba = 0; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_err(ni, "Failed to update main info table\n"); ++ nmbm_mark_block_color_info_table(ni, tmp_table_start_ba, ++ tmp_table_end_ba - 1); ++ ++ return true; ++ } ++ ++ /* Main info table has been successfully written, record its offset */ ++ ni->main_table_ba = main_table_start_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_creation(ni, true, main_table_start_ba, main_table_end_ba); ++ ++ /* ++ * Temporary info table becomes the new backup info table if it's ++ * not overwritten. ++ */ ++ if (main_table_end_ba <= tmp_table_start_ba) { ++ ni->backup_table_ba = tmp_table_start_ba; ++ ++ nlog_table_creation(ni, false, tmp_table_start_ba, ++ tmp_table_end_ba); ++ ++ return true; ++ } ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = main_table_end_ba; ++ ++ /* Try to reserve spare blocks for new main info table */ ++ success = nmbm_try_reserve_blocks(ni, main_table_end_ba, &write_ba, ++ ni->info_table_spare_blocks, 0, ++ ni->mapping_blocks_top_ba - ++ info_table_erasesize); ++ if (!success) { ++ /* There is no spare block. Only main table exists. */ ++ nlog_err(ni, "No room for backup info table\n"); ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Write new backup info table. */ ++ while (write_ba >= main_table_end_ba) { ++ WATCHDOG_RESET(); ++ ++ success = nmbm_write_info_table(ni, write_ba, ++ ni->mapping_blocks_top_ba, ++ &tmp_table_start_ba, ++ &tmp_table_end_ba); ++ if (success) ++ break; ++ ++ write_ba--; ++ } ++ ++ if (!success) { ++ nlog_err(ni, "No room for backup info table\n"); ++ ni->backup_table_ba = 0; ++ return true; ++ } ++ ++ /* Backup info table has been successfully written, record its offset */ ++ ni->backup_table_ba = tmp_table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = tmp_table_end_ba; ++ ++ /* Erase spare blocks of main table to clean possible interference data */ ++ nmbm_erase_range(ni, main_table_end_ba, ni->backup_table_ba); ++ ++ nlog_table_creation(ni, false, tmp_table_start_ba, tmp_table_end_ba); ++ ++ return true; ++} ++ ++/* ++ * nmbm_update_info_table_once - Update info table once ++ * @ni: NMBM instance structure ++ * @force: force update ++ * ++ * Update both main and backup info table. Return true if at least one info ++ * table has been successfully written. ++ * This function only try to update info table once regard less of the result. ++ */ ++static bool nmbm_update_info_table_once(struct nmbm_instance *ni, bool force) ++{ ++ uint32_t table_start_ba, table_end_ba; ++ uint32_t main_table_limit; ++ bool success; ++ ++ /* Do nothing if there is no change */ ++ if (!nmbm_generate_info_table_cache(ni) && !force) ++ return true; ++ ++ /* Check whether both two tables exist */ ++ if (!ni->backup_table_ba) { ++ main_table_limit = ni->mapping_blocks_top_ba; ++ goto write_main_table; ++ } ++ ++ nmbm_mark_block_color_mgmt(ni, ni->backup_table_ba, ++ ni->mapping_blocks_ba - 1); ++ ++ /* ++ * Write backup info table in its current range. ++ * Note that limit is set to mapping_blocks_top_off to provide as many ++ * spare blocks as possible for the backup table. If at last ++ * unmapped blocks are used by backup table, mapping_blocks_off will ++ * be adjusted. ++ */ ++ success = nmbm_write_info_table(ni, ni->backup_table_ba, ++ ni->mapping_blocks_top_ba, ++ &table_start_ba, &table_end_ba); ++ if (!success) { ++ /* ++ * There is nothing to do if failed to write backup table. ++ * Write the main table now. ++ */ ++ nlog_err(ni, "No room for backup table\n"); ++ ni->mapping_blocks_ba = ni->backup_table_ba; ++ ni->backup_table_ba = 0; ++ main_table_limit = ni->mapping_blocks_top_ba; ++ goto write_main_table; ++ } ++ ++ /* Backup table is successfully written, record its offset */ ++ ni->backup_table_ba = table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ /* The normal limit of main table */ ++ main_table_limit = ni->backup_table_ba; ++ ++ nlog_table_update(ni, false, table_start_ba, table_end_ba); ++ ++write_main_table: ++ if (!ni->main_table_ba) ++ goto rebuild_tables; ++ ++ if (!ni->backup_table_ba) ++ nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ++ ni->mapping_blocks_ba - 1); ++ else ++ nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ++ ni->backup_table_ba - 1); ++ ++ /* Write main info table in its current range */ ++ success = nmbm_write_info_table(ni, ni->main_table_ba, ++ main_table_limit, &table_start_ba, ++ &table_end_ba); ++ if (!success) { ++ /* If failed to write main table, go rescue procedure */ ++ if (!ni->backup_table_ba) ++ goto rebuild_tables; ++ ++ return nmbm_rescue_main_info_table(ni); ++ } ++ ++ /* Main info table is successfully written, record its offset */ ++ ni->main_table_ba = table_start_ba; ++ ++ /* Adjust mapping_blocks_off */ ++ if (!ni->backup_table_ba) ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ nmbm_mark_tables_clean(ni); ++ ++ nlog_table_update(ni, true, table_start_ba, table_end_ba); ++ ++ return true; ++ ++rebuild_tables: ++ return nmbm_rebuild_info_table(ni); ++} ++ ++/* ++ * nmbm_update_info_table - Update info table ++ * @ni: NMBM instance structure ++ * ++ * Update both main and backup info table. Return true if at least one table ++ * has been successfully written. ++ * This function will try to update info table repeatedly until no new bad ++ * block found during updating. ++ */ ++static bool nmbm_update_info_table(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ if (ni->protected) ++ return true; ++ ++ while (ni->block_state_changed || ni->block_mapping_changed) { ++ success = nmbm_update_info_table_once(ni, false); ++ if (!success) { ++ nlog_err(ni, "Failed to update info table\n"); ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_map_block - Map a bad block to a unused spare block ++ * @ni: NMBM instance structure ++ * @lb: logic block addr to map ++ */ ++static bool nmbm_map_block(struct nmbm_instance *ni, uint32_t lb) ++{ ++ uint32_t pb; ++ bool success; ++ ++ if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { ++ nlog_warn(ni, "No spare unmapped blocks.\n"); ++ return false; ++ } ++ ++ success = nmbm_block_walk(ni, false, ni->mapping_blocks_top_ba, &pb, 0, ++ ni->mapping_blocks_ba); ++ if (!success) { ++ nlog_warn(ni, "No spare unmapped blocks.\n"); ++ nmbm_update_info_table(ni); ++ ni->mapping_blocks_top_ba = ni->mapping_blocks_ba; ++ return false; ++ } ++ ++ ni->block_mapping[lb] = pb; ++ ni->mapping_blocks_top_ba--; ++ ni->block_mapping_changed++; ++ ++ nlog_info(ni, "Logic block %u mapped to physical blcok %u\n", lb, pb); ++ nmbm_mark_block_color_mapped(ni, pb); ++ ++ return true; ++} ++ ++/* ++ * nmbm_create_info_table - Create info table(s) ++ * @ni: NMBM instance structure ++ * ++ * This function assumes that the chip has no existing info table(s) ++ */ ++static bool nmbm_create_info_table(struct nmbm_instance *ni) ++{ ++ uint32_t lb; ++ bool success; ++ ++ /* Set initial mapping_blocks_top_off */ ++ success = nmbm_block_walk(ni, false, ni->signature_ba, ++ &ni->mapping_blocks_top_ba, 1, ++ ni->mgmt_start_ba); ++ if (!success) { ++ nlog_err(ni, "No room for spare blocks\n"); ++ return false; ++ } ++ ++ /* Generate info table cache */ ++ nmbm_generate_info_table_cache(ni); ++ ++ /* Write info table */ ++ success = nmbm_rebuild_info_table(ni); ++ if (!success) { ++ nlog_err(ni, "Failed to build info tables\n"); ++ return false; ++ } ++ ++ /* Remap bad block(s) at end of data area */ ++ for (lb = ni->data_block_count; lb < ni->mgmt_start_ba; lb++) { ++ success = nmbm_map_block(ni, lb); ++ if (!success) ++ break; ++ ++ ni->data_block_count++; ++ } ++ ++ /* If state table and/or mapping table changed, update info table. */ ++ success = nmbm_update_info_table(ni); ++ if (!success) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * nmbm_create_new - Create NMBM on a new chip ++ * @ni: NMBM instance structure ++ */ ++static bool nmbm_create_new(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ /* Determine the boundary of management blocks */ ++ ni->mgmt_start_ba = ni->block_count * (NMBM_MGMT_DIV - ni->lower.max_ratio) / NMBM_MGMT_DIV; ++ ++ if (ni->lower.max_reserved_blocks && ni->block_count - ni->mgmt_start_ba > ni->lower.max_reserved_blocks) ++ ni->mgmt_start_ba = ni->block_count - ni->lower.max_reserved_blocks; ++ ++ nlog_info(ni, "NMBM management region starts at block %u [0x%08llx]\n", ++ ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); ++ nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ni->block_count - 1); ++ ++ /* Fill block state table & mapping table */ ++ nmbm_scan_badblocks(ni); ++ nmbm_build_mapping_table(ni); ++ ++ /* Write signature */ ++ ni->signature.header.magic = NMBM_MAGIC_SIGNATURE; ++ ni->signature.header.version = NMBM_VER; ++ ni->signature.header.size = sizeof(ni->signature); ++ ni->signature.nand_size = ni->lower.size; ++ ni->signature.block_size = ni->lower.erasesize; ++ ni->signature.page_size = ni->lower.writesize; ++ ni->signature.spare_size = ni->lower.oobsize; ++ ni->signature.mgmt_start_pb = ni->mgmt_start_ba; ++ ni->signature.max_try_count = NMBM_TRY_COUNT; ++ nmbm_update_checksum(&ni->signature.header); ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) { ++ nlog_info(ni, "NMBM has been initialized in read-only mode\n"); ++ return true; ++ } ++ ++ success = nmbm_write_signature(ni, ni->mgmt_start_ba, ++ &ni->signature, &ni->signature_ba); ++ if (!success) { ++ nlog_err(ni, "Failed to write signature to a proper offset\n"); ++ return false; ++ } ++ ++ nlog_info(ni, "Signature has been written to block %u [0x%08llx]\n", ++ ni->signature_ba, ba2addr(ni, ni->signature_ba)); ++ nmbm_mark_block_color_signature(ni, ni->signature_ba); ++ ++ /* Write info table(s) */ ++ success = nmbm_create_info_table(ni); ++ if (success) { ++ nlog_info(ni, "NMBM has been successfully created\n"); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_check_info_table_header - Check if a info table header is valid ++ * @ni: NMBM instance structure ++ * @data: pointer to the info table header ++ */ ++static bool nmbm_check_info_table_header(struct nmbm_instance *ni, void *data) ++{ ++ struct nmbm_info_table_header *ifthdr = data; ++ ++ if (ifthdr->header.magic != NMBM_MAGIC_INFO_TABLE) ++ return false; ++ ++ if (ifthdr->header.size != ni->info_table_size) ++ return false; ++ ++ if (ifthdr->mapping_table_off - ifthdr->state_table_off < ni->state_table_size) ++ return false; ++ ++ if (ni->info_table_size - ifthdr->mapping_table_off < ni->mapping_table_size) ++ return false; ++ ++ return true; ++} ++ ++/* ++ * nmbm_check_info_table - Check if a whole info table is valid ++ * @ni: NMBM instance structure ++ * @start_ba: start block address of this table ++ * @end_ba: end block address of this table ++ * @data: pointer to the info table header ++ * @mapping_blocks_top_ba: return the block address of top remapped block ++ */ ++static bool nmbm_check_info_table(struct nmbm_instance *ni, uint32_t start_ba, ++ uint32_t end_ba, void *data, ++ uint32_t *mapping_blocks_top_ba) ++{ ++ struct nmbm_info_table_header *ifthdr = data; ++ int32_t *block_mapping = (int32_t *)((uintptr_t)data + ifthdr->mapping_table_off); ++ nmbm_bitmap_t *block_state = (nmbm_bitmap_t *)((uintptr_t)data + ifthdr->state_table_off); ++ uint32_t minimum_mapping_pb = ni->signature_ba; ++ uint32_t ba; ++ ++ for (ba = 0; ba < ni->data_block_count; ba++) { ++ if ((block_mapping[ba] >= ni->data_block_count && block_mapping[ba] < end_ba) || ++ block_mapping[ba] == ni->signature_ba) ++ return false; ++ ++ if (block_mapping[ba] >= end_ba && block_mapping[ba] < minimum_mapping_pb) ++ minimum_mapping_pb = block_mapping[ba]; ++ } ++ ++ for (ba = start_ba; ba < end_ba; ba++) { ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ continue; ++ ++ if (nmbm_get_block_state_raw(block_state, ba) != BLOCK_ST_GOOD) ++ return false; ++ } ++ ++ *mapping_blocks_top_ba = minimum_mapping_pb - 1; ++ ++ return true; ++} ++ ++/* ++ * nmbm_try_load_info_table - Try to load info table from a address ++ * @ni: NMBM instance structure ++ * @ba: start block address of the info table ++ * @eba: return the block address after end of the table ++ * @write_count: return the write count of this table ++ * @mapping_blocks_top_ba: return the block address of top remapped block ++ * @table_loaded: used to record whether ni->info_table has valid data ++ */ ++static bool nmbm_try_load_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t *eba, uint32_t *write_count, ++ uint32_t *mapping_blocks_top_ba, ++ bool table_loaded) ++{ ++ struct nmbm_info_table_header *ifthdr = (void *)ni->info_table_cache; ++ uint8_t *off = ni->info_table_cache; ++ uint32_t limit = ba + size2blk(ni, ni->info_table_size); ++ uint32_t start_ba = 0, chunksize, sizeremain = ni->info_table_size; ++ bool success, checkhdr = true; ++ int ret; ++ ++ while (sizeremain && ba < limit) { ++ WATCHDOG_RESET(); ++ ++ if (nmbm_get_block_state(ni, ba) != BLOCK_ST_GOOD) ++ goto next_block; ++ ++ if (nmbm_check_bad_phys_block(ni, ba)) { ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ goto next_block; ++ } ++ ++ chunksize = sizeremain; ++ if (chunksize > ni->lower.erasesize) ++ chunksize = ni->lower.erasesize; ++ ++ /* Assume block with ECC error has no info table data */ ++ ret = nmbn_read_data(ni, ba2addr(ni, ba), off, chunksize); ++ if (ret < 0) ++ goto skip_bad_block; ++ else if (ret > 0) ++ return false; ++ ++ if (checkhdr) { ++ success = nmbm_check_info_table_header(ni, off); ++ if (!success) ++ return false; ++ ++ start_ba = ba; ++ checkhdr = false; ++ } ++ ++ off += chunksize; ++ sizeremain -= chunksize; ++ ++ goto next_block; ++ ++ skip_bad_block: ++ /* Only mark bad in memory */ ++ nmbm_set_block_state(ni, ba, BLOCK_ST_BAD); ++ ++ next_block: ++ ba++; ++ } ++ ++ if (sizeremain) ++ return false; ++ ++ success = nmbm_check_header(ni->info_table_cache, ni->info_table_size); ++ if (!success) ++ return false; ++ ++ *eba = ba; ++ *write_count = ifthdr->write_count; ++ ++ success = nmbm_check_info_table(ni, start_ba, ba, ni->info_table_cache, ++ mapping_blocks_top_ba); ++ if (!success) ++ return false; ++ ++ if (!table_loaded || ifthdr->write_count > ni->info_table.write_count) { ++ memcpy(&ni->info_table, ifthdr, sizeof(ni->info_table)); ++ memcpy(ni->block_state, ++ (uint8_t *)ifthdr + ifthdr->state_table_off, ++ ni->state_table_size); ++ memcpy(ni->block_mapping, ++ (uint8_t *)ifthdr + ifthdr->mapping_table_off, ++ ni->mapping_table_size); ++ ni->info_table.write_count = ifthdr->write_count; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_search_info_table - Search info table from specific address ++ * @ni: NMBM instance structure ++ * @ba: start block address to search ++ * @limit: highest block address allowed for searching ++ * @table_start_ba: return the start block address of this table ++ * @table_end_ba: return the block address after end of this table ++ * @write_count: return the write count of this table ++ * @mapping_blocks_top_ba: return the block address of top remapped block ++ * @table_loaded: used to record whether ni->info_table has valid data ++ */ ++static bool nmbm_search_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit, uint32_t *table_start_ba, ++ uint32_t *table_end_ba, ++ uint32_t *write_count, ++ uint32_t *mapping_blocks_top_ba, ++ bool table_loaded) ++{ ++ bool success; ++ ++ while (ba < limit - size2blk(ni, ni->info_table_size)) { ++ WATCHDOG_RESET(); ++ ++ success = nmbm_try_load_info_table(ni, ba, table_end_ba, ++ write_count, ++ mapping_blocks_top_ba, ++ table_loaded); ++ if (success) { ++ *table_start_ba = ba; ++ return true; ++ } ++ ++ ba++; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_load_info_table - Load info table(s) from a chip ++ * @ni: NMBM instance structure ++ * @ba: start block address to search info table ++ * @limit: highest block address allowed for searching ++ */ ++static bool nmbm_load_info_table(struct nmbm_instance *ni, uint32_t ba, ++ uint32_t limit) ++{ ++ uint32_t main_table_end_ba, backup_table_end_ba, table_end_ba; ++ uint32_t main_mapping_blocks_top_ba, backup_mapping_blocks_top_ba; ++ uint32_t main_table_write_count, backup_table_write_count; ++ uint32_t i; ++ bool success; ++ ++ /* Set initial value */ ++ ni->main_table_ba = 0; ++ ni->backup_table_ba = 0; ++ ni->info_table.write_count = 0; ++ ni->mapping_blocks_top_ba = ni->signature_ba - 1; ++ ni->data_block_count = ni->signature.mgmt_start_pb; ++ ++ /* Find first info table */ ++ success = nmbm_search_info_table(ni, ba, limit, &ni->main_table_ba, ++ &main_table_end_ba, &main_table_write_count, ++ &main_mapping_blocks_top_ba, false); ++ if (!success) { ++ nlog_warn(ni, "No valid info table found\n"); ++ return false; ++ } ++ ++ table_end_ba = main_table_end_ba; ++ ++ nlog_table_found(ni, true, main_table_write_count, ni->main_table_ba, ++ main_table_end_ba); ++ ++ /* Find second info table */ ++ success = nmbm_search_info_table(ni, main_table_end_ba, limit, ++ &ni->backup_table_ba, &backup_table_end_ba, ++ &backup_table_write_count, &backup_mapping_blocks_top_ba, true); ++ if (!success) { ++ nlog_warn(ni, "Second info table not found\n"); ++ } else { ++ table_end_ba = backup_table_end_ba; ++ ++ nlog_table_found(ni, false, backup_table_write_count, ++ ni->backup_table_ba, backup_table_end_ba); ++ } ++ ++ /* Pick mapping_blocks_top_ba */ ++ if (!ni->backup_table_ba) { ++ ni->mapping_blocks_top_ba= main_mapping_blocks_top_ba; ++ } else { ++ if (main_table_write_count >= backup_table_write_count) ++ ni->mapping_blocks_top_ba = main_mapping_blocks_top_ba; ++ else ++ ni->mapping_blocks_top_ba = backup_mapping_blocks_top_ba; ++ } ++ ++ /* Set final mapping_blocks_ba */ ++ ni->mapping_blocks_ba = table_end_ba; ++ ++ /* Set final data_block_count */ ++ for (i = ni->signature.mgmt_start_pb; i > 0; i--) { ++ if (ni->block_mapping[i - 1] >= 0) { ++ ni->data_block_count = i; ++ break; ++ } ++ } ++ ++ /* Debug purpose: mark mapped blocks and bad blocks */ ++ for (i = 0; i < ni->data_block_count; i++) { ++ if (ni->block_mapping[i] > ni->mapping_blocks_top_ba) ++ nmbm_mark_block_color_mapped(ni, ni->block_mapping[i]); ++ } ++ ++ for (i = 0; i < ni->block_count; i++) { ++ if (nmbm_get_block_state(ni, i) == BLOCK_ST_BAD) ++ nmbm_mark_block_color_bad(ni, i); ++ } ++ ++ /* Regenerate the info table cache from the final selected info table */ ++ nmbm_generate_info_table_cache(ni); ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) ++ return true; ++ ++ /* ++ * If only one table exists, try to write another table. ++ * If two tables have different write count, try to update info table ++ */ ++ if (!ni->backup_table_ba) { ++ success = nmbm_rescue_single_info_table(ni); ++ } else if (main_table_write_count != backup_table_write_count) { ++ /* Mark state & mapping tables changed */ ++ ni->block_state_changed = 1; ++ ni->block_mapping_changed = 1; ++ ++ success = nmbm_update_single_info_table(ni, ++ main_table_write_count < backup_table_write_count); ++ } else { ++ success = true; ++ } ++ ++ /* ++ * If there is no spare unmapped blocks, or still only one table ++ * exists, set the chip to read-only ++ */ ++ if (ni->mapping_blocks_ba == ni->mapping_blocks_top_ba) { ++ nlog_warn(ni, "No spare unmapped blocks. Device is now read-only\n"); ++ ni->protected = 1; ++ } else if (!success) { ++ nlog_warn(ni, "Only one info table found. Device is now read-only\n"); ++ ni->protected = 1; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_load_existing - Load NMBM from a new chip ++ * @ni: NMBM instance structure ++ */ ++static bool nmbm_load_existing(struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ /* Calculate the boundary of management blocks */ ++ ni->mgmt_start_ba = ni->signature.mgmt_start_pb; ++ ++ nlog_debug(ni, "NMBM management region starts at block %u [0x%08llx]\n", ++ ni->mgmt_start_ba, ba2addr(ni, ni->mgmt_start_ba)); ++ nmbm_mark_block_color_mgmt(ni, ni->mgmt_start_ba, ++ ni->signature_ba - 1); ++ ++ /* Look for info table(s) */ ++ success = nmbm_load_info_table(ni, ni->mgmt_start_ba, ++ ni->signature_ba); ++ if (success) { ++ nlog_info(ni, "NMBM has been successfully attached %s\n", ++ (ni->lower.flags & NMBM_F_READ_ONLY) ? "in read-only mode" : ""); ++ return true; ++ } ++ ++ if (!(ni->lower.flags & NMBM_F_CREATE)) ++ return false; ++ ++ /* Fill block state table & mapping table */ ++ nmbm_scan_badblocks(ni); ++ nmbm_build_mapping_table(ni); ++ ++ if (ni->lower.flags & NMBM_F_READ_ONLY) { ++ nlog_info(ni, "NMBM has been initialized in read-only mode\n"); ++ return true; ++ } ++ ++ /* Write info table(s) */ ++ success = nmbm_create_info_table(ni); ++ if (success) { ++ nlog_info(ni, "NMBM has been successfully created\n"); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * nmbm_find_signature - Find signature in the lower NAND chip ++ * @ni: NMBM instance structure ++ * @signature_ba: used for storing block address of the signature ++ * @signature_ba: return the actual block address of signature block ++ * ++ * Find a valid signature from a specific range in the lower NAND chip, ++ * from bottom (highest address) to top (lowest address) ++ * ++ * Return true if found. ++ */ ++static bool nmbm_find_signature(struct nmbm_instance *ni, ++ struct nmbm_signature *signature, ++ uint32_t *signature_ba) ++{ ++ struct nmbm_signature sig; ++ uint64_t off, addr; ++ uint32_t block_count, ba, limit; ++ bool success; ++ int ret; ++ ++ /* Calculate top and bottom block address */ ++ block_count = ni->lower.size >> ni->erasesize_shift; ++ ba = block_count; ++ limit = (block_count / NMBM_MGMT_DIV) * (NMBM_MGMT_DIV - ni->lower.max_ratio); ++ if (ni->lower.max_reserved_blocks && block_count - limit > ni->lower.max_reserved_blocks) ++ limit = block_count - ni->lower.max_reserved_blocks; ++ ++ while (ba >= limit) { ++ WATCHDOG_RESET(); ++ ++ ba--; ++ addr = ba2addr(ni, ba); ++ ++ if (nmbm_check_bad_phys_block(ni, ba)) ++ continue; ++ ++ /* Check every page. ++ * As long as at leaset one page contains valid signature, ++ * the block is treated as a valid signature block. ++ */ ++ for (off = 0; off < ni->lower.erasesize; ++ off += ni->lower.writesize) { ++ WATCHDOG_RESET(); ++ ++ ret = nmbn_read_data(ni, addr + off, &sig, ++ sizeof(sig)); ++ if (ret) ++ continue; ++ ++ /* Check for header size and checksum */ ++ success = nmbm_check_header(&sig, sizeof(sig)); ++ if (!success) ++ continue; ++ ++ /* Check for header magic */ ++ if (sig.header.magic == NMBM_MAGIC_SIGNATURE) { ++ /* Found it */ ++ memcpy(signature, &sig, sizeof(sig)); ++ *signature_ba = ba; ++ return true; ++ } ++ } ++ }; ++ ++ return false; ++} ++ ++/* ++ * is_power_of_2_u64 - Check whether a 64-bit integer is power of 2 ++ * @n: number to check ++ */ ++static bool is_power_of_2_u64(uint64_t n) ++{ ++ return (n != 0 && ((n & (n - 1)) == 0)); ++} ++ ++/* ++ * nmbm_check_lower_members - Validate the members of lower NAND device ++ * @nld: Lower NAND chip structure ++ */ ++static bool nmbm_check_lower_members(struct nmbm_lower_device *nld) ++{ ++ ++ if (!nld->size || !is_power_of_2_u64(nld->size)) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "Chip size %llu is not valid\n", nld->size); ++ return false; ++ } ++ ++ if (!nld->erasesize || !is_power_of_2(nld->erasesize)) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "Block size %u is not valid\n", nld->erasesize); ++ return false; ++ } ++ ++ if (!nld->writesize || !is_power_of_2(nld->writesize)) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "Page size %u is not valid\n", nld->writesize); ++ return false; ++ } ++ ++ if (!nld->oobsize) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "Page spare size %u is not valid\n", nld->oobsize); ++ return false; ++ } ++ ++ if (!nld->read_page) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, "read_page() is required\n"); ++ return false; ++ } ++ ++ if (!(nld->flags & NMBM_F_READ_ONLY) && (!nld->write_page || !nld->erase_block)) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "write_page() and erase_block() are required\n"); ++ return false; ++ } ++ ++ /* Data sanity check */ ++ if (!nld->max_ratio) ++ nld->max_ratio = 1; ++ ++ if (nld->max_ratio >= NMBM_MGMT_DIV - 1) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "max ratio %u is invalid\n", nld->max_ratio); ++ return false; ++ } ++ ++ if (nld->max_reserved_blocks && nld->max_reserved_blocks < NMBM_MGMT_BLOCKS_MIN) { ++ nmbm_log_lower(nld, NMBM_LOG_ERR, ++ "max reserved blocks %u is too small\n", nld->max_reserved_blocks); ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * nmbm_calc_structure_size - Calculate the instance structure size ++ * @nld: NMBM lower device structure ++ */ ++size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld) ++{ ++ uint32_t state_table_size, mapping_table_size, info_table_size; ++ uint32_t block_count; ++ ++ block_count = nmbm_lldiv(nld->size, nld->erasesize); ++ ++ /* Calculate info table size */ ++ state_table_size = ((block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / ++ NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; ++ mapping_table_size = block_count * sizeof(int32_t); ++ ++ info_table_size = NMBM_ALIGN(sizeof(struct nmbm_info_table_header), ++ nld->writesize); ++ info_table_size += NMBM_ALIGN(state_table_size, nld->writesize); ++ info_table_size += NMBM_ALIGN(mapping_table_size, nld->writesize); ++ ++ return info_table_size + state_table_size + mapping_table_size + ++ nld->writesize + nld->oobsize + sizeof(struct nmbm_instance); ++} ++ ++/* ++ * nmbm_init_structure - Initialize members of instance structure ++ * @ni: NMBM instance structure ++ */ ++static void nmbm_init_structure(struct nmbm_instance *ni) ++{ ++ uint32_t pages_per_block, blocks_per_chip; ++ uintptr_t ptr; ++ ++ pages_per_block = ni->lower.erasesize / ni->lower.writesize; ++ blocks_per_chip = nmbm_lldiv(ni->lower.size, ni->lower.erasesize); ++ ++ ni->rawpage_size = ni->lower.writesize + ni->lower.oobsize; ++ ni->rawblock_size = pages_per_block * ni->rawpage_size; ++ ni->rawchip_size = blocks_per_chip * ni->rawblock_size; ++ ++ ni->writesize_mask = ni->lower.writesize - 1; ++ ni->erasesize_mask = ni->lower.erasesize - 1; ++ ++ ni->writesize_shift = ffs(ni->lower.writesize) - 1; ++ ni->erasesize_shift = ffs(ni->lower.erasesize) - 1; ++ ++ /* Calculate number of block this chip */ ++ ni->block_count = ni->lower.size >> ni->erasesize_shift; ++ ++ /* Calculate info table size */ ++ ni->state_table_size = ((ni->block_count + NMBM_BITMAP_BLOCKS_PER_UNIT - 1) / ++ NMBM_BITMAP_BLOCKS_PER_UNIT) * NMBM_BITMAP_UNIT_SIZE; ++ ni->mapping_table_size = ni->block_count * sizeof(*ni->block_mapping); ++ ++ ni->info_table_size = NMBM_ALIGN(sizeof(ni->info_table), ++ ni->lower.writesize); ++ ni->info_table.state_table_off = ni->info_table_size; ++ ++ ni->info_table_size += NMBM_ALIGN(ni->state_table_size, ++ ni->lower.writesize); ++ ni->info_table.mapping_table_off = ni->info_table_size; ++ ++ ni->info_table_size += NMBM_ALIGN(ni->mapping_table_size, ++ ni->lower.writesize); ++ ++ ni->info_table_spare_blocks = nmbm_get_spare_block_count( ++ size2blk(ni, ni->info_table_size)); ++ ++ /* Assign memory to members */ ++ ptr = (uintptr_t)ni + sizeof(*ni); ++ ++ ni->info_table_cache = (void *)ptr; ++ ptr += ni->info_table_size; ++ ++ ni->block_state = (void *)ptr; ++ ptr += ni->state_table_size; ++ ++ ni->block_mapping = (void *)ptr; ++ ptr += ni->mapping_table_size; ++ ++ ni->page_cache = (uint8_t *)ptr; ++ ++ /* Initialize block state table */ ++ ni->block_state_changed = 0; ++ memset(ni->block_state, 0xff, ni->state_table_size); ++ ++ /* Initialize block mapping table */ ++ ni->block_mapping_changed = 0; ++} ++ ++/* ++ * nmbm_attach - Attach to a lower device ++ * @nld: NMBM lower device structure ++ * @ni: NMBM instance structure ++ */ ++int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni) ++{ ++ bool success; ++ ++ if (!nld || !ni) ++ return -EINVAL; ++ ++ /* Set default log level */ ++ ni->log_display_level = NMBM_DEFAULT_LOG_LEVEL; ++ ++ /* Check lower members */ ++ success = nmbm_check_lower_members(nld); ++ if (!success) ++ return -EINVAL; ++ ++ /* Initialize NMBM instance */ ++ memcpy(&ni->lower, nld, sizeof(struct nmbm_lower_device)); ++ nmbm_init_structure(ni); ++ ++ success = nmbm_find_signature(ni, &ni->signature, &ni->signature_ba); ++ if (!success) { ++ if (!(nld->flags & NMBM_F_CREATE)) { ++ nlog_err(ni, "Signature not found\n"); ++ return -ENODEV; ++ } ++ ++ success = nmbm_create_new(ni); ++ if (!success) ++ return -ENODEV; ++ ++ return 0; ++ } ++ ++ nlog_info(ni, "Signature found at block %u [0x%08llx]\n", ++ ni->signature_ba, ba2addr(ni, ni->signature_ba)); ++ nmbm_mark_block_color_signature(ni, ni->signature_ba); ++ ++ if (ni->signature.header.version != NMBM_VER) { ++ nlog_err(ni, "NMBM version %u.%u is not supported\n", ++ NMBM_VERSION_MAJOR_GET(ni->signature.header.version), ++ NMBM_VERSION_MINOR_GET(ni->signature.header.version)); ++ return -EINVAL; ++ } ++ ++ if (ni->signature.nand_size != nld->size || ++ ni->signature.block_size != nld->erasesize || ++ ni->signature.page_size != nld->writesize || ++ ni->signature.spare_size != nld->oobsize) { ++ nlog_err(ni, "NMBM configuration mismatch\n"); ++ return -EINVAL; ++ } ++ ++ success = nmbm_load_existing(ni); ++ if (!success) ++ return -ENODEV; ++ ++ return 0; ++} ++ ++/* ++ * nmbm_detach - Detach from a lower device, and save all tables ++ * @ni: NMBM instance structure ++ */ ++int nmbm_detach(struct nmbm_instance *ni) ++{ ++ if (!ni) ++ return -EINVAL; ++ ++ if (!(ni->lower.flags & NMBM_F_READ_ONLY)) ++ nmbm_update_info_table(ni); ++ ++ nmbm_mark_block_color_normal(ni, 0, ni->block_count - 1); ++ ++ return 0; ++} ++ ++/* ++ * nmbm_erase_logic_block - Erase a logic block ++ * @ni: NMBM instance structure ++ * @nmbm_erase_logic_block: logic block address ++ * ++ * Logic block will be mapped to physical block before erasing. ++ * Bad block found during erasinh will be remapped to a good block if there is ++ * still at least one good spare block available. ++ */ ++static int nmbm_erase_logic_block(struct nmbm_instance *ni, uint32_t block_addr) ++{ ++ uint32_t pb; ++ bool success; ++ ++retry: ++ /* Map logic block to physical block */ ++ pb = ni->block_mapping[block_addr]; ++ ++ /* Whether the logic block is good (has valid mapping) */ ++ if ((int32_t)pb < 0) { ++ nlog_debug(ni, "Logic block %u is a bad block\n", block_addr); ++ return -EIO; ++ } ++ ++ /* Remap logic block if current physical block is a bad block */ ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD || ++ nmbm_get_block_state(ni, pb) == BLOCK_ST_NEED_REMAP) ++ goto remap_logic_block; ++ ++ /* Insurance to detect unexpected bad block marked by user */ ++ if (nmbm_check_bad_phys_block(ni, pb)) { ++ nlog_warn(ni, "Found unexpected bad block possibly marked by user\n"); ++ nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); ++ goto remap_logic_block; ++ } ++ ++ success = nmbm_erase_block_and_check(ni, pb); ++ if (success) ++ return 0; ++ ++ /* Mark bad block */ ++ nmbm_mark_phys_bad_block(ni, pb); ++ nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); ++ ++remap_logic_block: ++ /* Try to assign a new block */ ++ success = nmbm_map_block(ni, block_addr); ++ if (!success) { ++ /* Mark logic block unusable, and update info table */ ++ ni->block_mapping[block_addr] = -1; ++ if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) ++ nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); ++ nmbm_update_info_table(ni); ++ return -EIO; ++ } ++ ++ /* Update info table before erasing */ ++ if (nmbm_get_block_state(ni, pb) != BLOCK_ST_NEED_REMAP) ++ nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); ++ nmbm_update_info_table(ni); ++ ++ goto retry; ++} ++ ++/* ++ * nmbm_erase_block_range - Erase logic blocks ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @size: erase range ++ * @failed_addr: return failed block address if error occurs ++ */ ++int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, ++ uint64_t size, uint64_t *failed_addr) ++{ ++ uint32_t start_ba, end_ba; ++ int ret; ++ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ if (addr + size > ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Erase range 0xllxu is too large\n", size); ++ return -EINVAL; ++ } ++ ++ if (!size) { ++ nlog_warn(ni, "No blocks to be erased\n"); ++ return 0; ++ } ++ ++ start_ba = addr2ba(ni, addr); ++ end_ba = addr2ba(ni, addr + size - 1); ++ ++ while (start_ba <= end_ba) { ++ WATCHDOG_RESET(); ++ ++ ret = nmbm_erase_logic_block(ni, start_ba); ++ if (ret) { ++ if (failed_addr) ++ *failed_addr = ba2addr(ni, start_ba); ++ return ret; ++ } ++ ++ start_ba++; ++ } ++ ++ return 0; ++} ++ ++/* ++ * nmbm_read_logic_page - Read page based on logic address ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @data: buffer to store main data. optional. ++ * @oob: buffer to store oob data. optional. ++ * @mode: read mode ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++static int nmbm_read_logic_page(struct nmbm_instance *ni, uint64_t addr, ++ void *data, void *oob, enum nmbm_oob_mode mode) ++{ ++ uint32_t lb, pb, offset; ++ uint64_t paddr; ++ ++ /* Extract block address and in-block offset */ ++ lb = addr2ba(ni, addr); ++ offset = addr & ni->erasesize_mask; ++ ++ /* Map logic block to physical block */ ++ pb = ni->block_mapping[lb]; ++ ++ /* Whether the logic block is good (has valid mapping) */ ++ if ((int32_t)pb < 0) { ++ nlog_debug(ni, "Logic block %u is a bad block\n", lb); ++ return -EIO; ++ } ++ ++ /* Fail if physical block is marked bad */ ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) ++ return -EIO; ++ ++ /* Assemble new address */ ++ paddr = ba2addr(ni, pb) + offset; ++ ++ return nmbm_read_phys_page(ni, paddr, data, oob, mode); ++} ++ ++/* ++ * nmbm_read_single_page - Read one page based on logic address ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @data: buffer to store main data. optional. ++ * @oob: buffer to store oob data. optional. ++ * @mode: read mode ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, ++ void *oob, enum nmbm_oob_mode mode) ++{ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ return nmbm_read_logic_page(ni, addr, data, oob, mode); ++} ++ ++/* ++ * nmbm_read_range - Read data without oob ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @size: data size to read ++ * @data: buffer to store main data to be read ++ * @mode: read mode ++ * @retlen: return actual data size read ++ * ++ * Return 0 for success, positive value for corrected bitflip count, ++ * -EBADMSG for ecc error, other negative values for other errors ++ */ ++int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, ++ void *data, enum nmbm_oob_mode mode, size_t *retlen) ++{ ++ uint64_t off = addr; ++ uint8_t *ptr = data; ++ size_t sizeremain = size, chunksize, leading; ++ bool has_ecc_err = false; ++ int ret, max_bitflips = 0; ++ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ if (addr + size > ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Read range 0x%llx is too large\n", size); ++ return -EINVAL; ++ } ++ ++ if (!size) { ++ nlog_warn(ni, "No data to be read\n"); ++ return 0; ++ } ++ ++ while (sizeremain) { ++ WATCHDOG_RESET(); ++ ++ leading = off & ni->writesize_mask; ++ chunksize = ni->lower.writesize - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ if (chunksize == ni->lower.writesize) { ++ ret = nmbm_read_logic_page(ni, off - leading, ptr, ++ NULL, mode); ++ if (ret < 0 && ret != -EBADMSG) ++ break; ++ } else { ++ ret = nmbm_read_logic_page(ni, off - leading, ++ ni->page_cache, NULL, ++ mode); ++ if (ret < 0 && ret != -EBADMSG) ++ break; ++ ++ memcpy(ptr, ni->page_cache + leading, chunksize); ++ } ++ ++ if (ret == -EBADMSG) ++ has_ecc_err = true; ++ ++ if (ret > max_bitflips) ++ max_bitflips = ret; ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ if (retlen) ++ *retlen = size - sizeremain; ++ ++ if (ret < 0 && ret != -EBADMSG) ++ return ret; ++ ++ if (has_ecc_err) ++ return -EBADMSG; ++ ++ return max_bitflips; ++} ++ ++/* ++ * nmbm_write_logic_page - Read page based on logic address ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @data: buffer contains main data. optional. ++ * @oob: buffer contains oob data. optional. ++ * @mode: write mode ++ */ ++static int nmbm_write_logic_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, const void *oob, ++ enum nmbm_oob_mode mode) ++{ ++ uint32_t lb, pb, offset; ++ uint64_t paddr; ++ bool success; ++ ++ /* Extract block address and in-block offset */ ++ lb = addr2ba(ni, addr); ++ offset = addr & ni->erasesize_mask; ++ ++ /* Map logic block to physical block */ ++ pb = ni->block_mapping[lb]; ++ ++ /* Whether the logic block is good (has valid mapping) */ ++ if ((int32_t)pb < 0) { ++ nlog_debug(ni, "Logic block %u is a bad block\n", lb); ++ return -EIO; ++ } ++ ++ /* Fail if physical block is marked bad */ ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) ++ return -EIO; ++ ++ /* Assemble new address */ ++ paddr = ba2addr(ni, pb) + offset; ++ ++ success = nmbm_write_phys_page(ni, paddr, data, oob, mode); ++ if (success) ++ return 0; ++ ++ /* ++ * Do not remap bad block here. Just mark this block in state table. ++ * Remap this block on erasing. ++ */ ++ nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP); ++ nmbm_update_info_table(ni); ++ ++ return -EIO; ++} ++ ++/* ++ * nmbm_panic_write_logic_page - Panic write page based on logic address ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @data: buffer contains main data. optional. ++ */ ++static int nmbm_panic_write_logic_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data) ++{ ++ uint32_t lb, pb, offset; ++ uint64_t paddr; ++ bool success; ++ ++ /* Extract block address and in-block offset */ ++ lb = addr2ba(ni, addr); ++ offset = addr & ni->erasesize_mask; ++ ++ /* Map logic block to physical block */ ++ pb = ni->block_mapping[lb]; ++ ++ /* Whether the logic block is good (has valid mapping) */ ++ if ((int32_t)pb < 0) { ++ nlog_debug(ni, "Logic block %u is a bad block\n", lb); ++ return -EIO; ++ } ++ ++ /* Fail if physical block is marked bad */ ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) ++ return -EIO; ++ ++ /* Assemble new address */ ++ paddr = ba2addr(ni, pb) + offset; ++ ++ success = nmbm_panic_write_phys_page(ni, paddr, data); ++ if (success) ++ return 0; ++ ++ /* ++ * Do not remap bad block here. Just mark this block in state table. ++ * Remap this block on erasing. ++ */ ++ nmbm_set_block_state(ni, pb, BLOCK_ST_NEED_REMAP); ++ nmbm_update_info_table(ni); ++ ++ return -EIO; ++} ++ ++/* ++ * nmbm_write_single_page - Write one page based on logic address ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @data: buffer contains main data. optional. ++ * @oob: buffer contains oob data. optional. ++ * @mode: write mode ++ */ ++int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, const void *oob, ++ enum nmbm_oob_mode mode) ++{ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ return nmbm_write_logic_page(ni, addr, data, oob, mode); ++} ++ ++/* ++ * nmbm_panic_write_single_page - Panic write one page based on logic address ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @data: buffer contains main data. optional. ++ */ ++int nmbm_panic_write_single_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data) ++{ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ return nmbm_panic_write_logic_page(ni, addr, data); ++} ++ ++/* ++ * nmbm_write_range - Write data without oob ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ * @size: data size to write ++ * @data: buffer contains data to be written ++ * @mode: write mode ++ * @retlen: return actual data size written ++ */ ++int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, ++ const void *data, enum nmbm_oob_mode mode, ++ size_t *retlen) ++{ ++ uint64_t off = addr; ++ const uint8_t *ptr = data; ++ size_t sizeremain = size, chunksize, leading; ++ int ret; ++ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ if (addr + size > ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Write size 0x%zx is too large\n", size); ++ return -EINVAL; ++ } ++ ++ if (!size) { ++ nlog_warn(ni, "No data to be written\n"); ++ return 0; ++ } ++ ++ while (sizeremain) { ++ WATCHDOG_RESET(); ++ ++ leading = off & ni->writesize_mask; ++ chunksize = ni->lower.writesize - leading; ++ if (chunksize > sizeremain) ++ chunksize = sizeremain; ++ ++ if (chunksize == ni->lower.writesize) { ++ ret = nmbm_write_logic_page(ni, off - leading, ptr, ++ NULL, mode); ++ if (ret) ++ break; ++ } else { ++ memset(ni->page_cache, 0xff, leading); ++ memcpy(ni->page_cache + leading, ptr, chunksize); ++ ++ ret = nmbm_write_logic_page(ni, off - leading, ++ ni->page_cache, NULL, ++ mode); ++ if (ret) ++ break; ++ } ++ ++ off += chunksize; ++ ptr += chunksize; ++ sizeremain -= chunksize; ++ } ++ ++ if (retlen) ++ *retlen = size - sizeremain; ++ ++ return ret; ++} ++ ++/* ++ * nmbm_check_bad_block - Check whether a logic block is usable ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ */ ++int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr) ++{ ++ uint32_t lb, pb; ++ ++ if (!ni) ++ return -EINVAL; ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ lb = addr2ba(ni, addr); ++ ++ /* Map logic block to physical block */ ++ pb = ni->block_mapping[lb]; ++ ++ if ((int32_t)pb < 0) ++ return 1; ++ ++ if (nmbm_get_block_state(ni, pb) == BLOCK_ST_BAD) ++ return 1; ++ ++ return 0; ++} ++ ++/* ++ * nmbm_mark_bad_block - Mark a logic block unusable ++ * @ni: NMBM instance structure ++ * @addr: logic linear address ++ */ ++int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr) ++{ ++ uint32_t lb, pb; ++ ++ if (!ni) ++ return -EINVAL; ++ ++ /* Sanity check */ ++ if (ni->protected || (ni->lower.flags & NMBM_F_READ_ONLY)) { ++ nlog_debug(ni, "Device is forced read-only\n"); ++ return -EROFS; ++ } ++ ++ if (addr >= ba2addr(ni, ni->data_block_count)) { ++ nlog_err(ni, "Address 0x%llx is invalid\n", addr); ++ return -EINVAL; ++ } ++ ++ lb = addr2ba(ni, addr); ++ ++ /* Map logic block to physical block */ ++ pb = ni->block_mapping[lb]; ++ ++ if ((int32_t)pb < 0) ++ return 0; ++ ++ ni->block_mapping[lb] = -1; ++ nmbm_mark_phys_bad_block(ni, pb); ++ nmbm_set_block_state(ni, pb, BLOCK_ST_BAD); ++ nmbm_update_info_table(ni); ++ ++ return 0; ++} ++ ++/* ++ * nmbm_get_avail_size - Get available user data size ++ * @ni: NMBM instance structure ++ */ ++uint64_t nmbm_get_avail_size(struct nmbm_instance *ni) ++{ ++ if (!ni) ++ return 0; ++ ++ return (uint64_t)ni->data_block_count << ni->erasesize_shift; ++} ++ ++/* ++ * nmbm_get_lower_device - Get lower device structure ++ * @ni: NMBM instance structure ++ * @nld: pointer to hold the data of lower device structure ++ */ ++int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld) ++{ ++ if (!ni) ++ return -EINVAL; ++ ++ if (nld) ++ memcpy(nld, &ni->lower, sizeof(*nld)); ++ ++ return 0; ++} ++ ++#include "nmbm-debug.inl" +--- a/drivers/mtd/nmbm/nmbm-debug.h ++++ b/drivers/mtd/nmbm/nmbm-debug.h +@@ -0,0 +1,37 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Debug addons for NAND Mapped-block Management (NMBM) ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _NMBM_DEBUG_H_ ++#define _NMBM_DEBUG_H_ ++ ++#include "nmbm-private.h" ++ ++#define nmbm_mark_block_color_normal(ni, start_ba, end_ba) ++#define nmbm_mark_block_color_bad(ni, ba) ++#define nmbm_mark_block_color_mgmt(ni, start_ba, end_ba) ++#define nmbm_mark_block_color_signature(ni, ba) ++#define nmbm_mark_block_color_info_table(ni, start_ba, end_ba) ++#define nmbm_mark_block_color_mapped(ni, ba) ++ ++uint32_t nmbm_debug_get_block_state(struct nmbm_instance *ni, uint32_t ba); ++char nmbm_debug_get_phys_block_type(struct nmbm_instance *ni, uint32_t ba); ++ ++enum nmmb_block_type { ++ NMBM_BLOCK_GOOD_DATA, ++ NMBM_BLOCK_GOOD_MGMT, ++ NMBM_BLOCK_BAD, ++ NMBM_BLOCK_MAIN_INFO_TABLE, ++ NMBM_BLOCK_BACKUP_INFO_TABLE, ++ NMBM_BLOCK_REMAPPED, ++ NMBM_BLOCK_SIGNATURE, ++ ++ __NMBM_BLOCK_TYPE_MAX ++}; ++ ++#endif /* _NMBM_DEBUG_H_ */ +--- a/drivers/mtd/nmbm/nmbm-debug.inl ++++ b/drivers/mtd/nmbm/nmbm-debug.inl +@@ -0,0 +1,39 @@ ++ ++uint32_t nmbm_debug_get_block_state(struct nmbm_instance *ni, uint32_t ba) ++{ ++ return nmbm_get_block_state(ni, ba); ++} ++ ++char nmbm_debug_get_phys_block_type(struct nmbm_instance *ni, uint32_t ba) ++{ ++ uint32_t eba, limit; ++ bool success; ++ ++ if (nmbm_get_block_state(ni, ba) == BLOCK_ST_BAD) ++ return NMBM_BLOCK_BAD; ++ ++ if (ba < ni->data_block_count) ++ return NMBM_BLOCK_GOOD_DATA; ++ ++ if (ba == ni->signature_ba) ++ return NMBM_BLOCK_SIGNATURE; ++ ++ if (ni->main_table_ba) { ++ limit = ni->backup_table_ba ? ni->backup_table_ba : ++ ni->mapping_blocks_ba; ++ ++ success = nmbm_block_walk_asc(ni, ni->main_table_ba, &eba, ++ size2blk(ni, ni->info_table_size), limit); ++ ++ if (success && ba >= ni->main_table_ba && ba < eba) ++ return NMBM_BLOCK_MAIN_INFO_TABLE; ++ } ++ ++ if (ba >= ni->backup_table_ba && ba < ni->mapping_blocks_ba) ++ return NMBM_BLOCK_BACKUP_INFO_TABLE; ++ ++ if (ba > ni->mapping_blocks_top_ba && ba < ni->signature_ba) ++ return NMBM_BLOCK_REMAPPED; ++ ++ return NMBM_BLOCK_GOOD_MGMT; ++} +--- a/drivers/mtd/nmbm/nmbm-private.h ++++ b/drivers/mtd/nmbm/nmbm-private.h +@@ -0,0 +1,137 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Definitions for NAND Mapped-block Management (NMBM) ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _NMBM_PRIVATE_H_ ++#define _NMBM_PRIVATE_H_ ++ ++#include ++ ++#define NMBM_MAGIC_SIGNATURE 0x304d4d4e /* NMM0 */ ++#define NMBM_MAGIC_INFO_TABLE 0x314d4d4e /* NMM1 */ ++ ++#define NMBM_VERSION_MAJOR_S 0 ++#define NMBM_VERSION_MAJOR_M 0xffff ++#define NMBM_VERSION_MINOR_S 16 ++#define NMBM_VERSION_MINOR_M 0xffff ++#define NMBM_VERSION_MAKE(major, minor) (((major) & NMBM_VERSION_MAJOR_M) | \ ++ (((minor) & NMBM_VERSION_MINOR_M) << \ ++ NMBM_VERSION_MINOR_S)) ++#define NMBM_VERSION_MAJOR_GET(ver) (((ver) >> NMBM_VERSION_MAJOR_S) & \ ++ NMBM_VERSION_MAJOR_M) ++#define NMBM_VERSION_MINOR_GET(ver) (((ver) >> NMBM_VERSION_MINOR_S) & \ ++ NMBM_VERSION_MINOR_M) ++ ++typedef uint32_t nmbm_bitmap_t; ++#define NMBM_BITMAP_UNIT_SIZE (sizeof(nmbm_bitmap_t)) ++#define NMBM_BITMAP_BITS_PER_BLOCK 2 ++#define NMBM_BITMAP_BITS_PER_UNIT (8 * sizeof(nmbm_bitmap_t)) ++#define NMBM_BITMAP_BLOCKS_PER_UNIT (NMBM_BITMAP_BITS_PER_UNIT / \ ++ NMBM_BITMAP_BITS_PER_BLOCK) ++ ++#define NMBM_SPARE_BLOCK_MULTI 1 ++#define NMBM_SPARE_BLOCK_DIV 2 ++#define NMBM_SPARE_BLOCK_MIN 2 ++ ++#define NMBM_MGMT_DIV 16 ++#define NMBM_MGMT_BLOCKS_MIN 32 ++ ++#define NMBM_TRY_COUNT 3 ++ ++#define BLOCK_ST_BAD 0 ++#define BLOCK_ST_NEED_REMAP 2 ++#define BLOCK_ST_GOOD 3 ++#define BLOCK_ST_MASK 3 ++ ++struct nmbm_header { ++ uint32_t magic; ++ uint32_t version; ++ uint32_t size; ++ uint32_t checksum; ++}; ++ ++struct nmbm_signature { ++ struct nmbm_header header; ++ uint64_t nand_size; ++ uint32_t block_size; ++ uint32_t page_size; ++ uint32_t spare_size; ++ uint32_t mgmt_start_pb; ++ uint8_t max_try_count; ++ uint8_t padding[3]; ++}; ++ ++struct nmbm_info_table_header { ++ struct nmbm_header header; ++ uint32_t write_count; ++ uint32_t state_table_off; ++ uint32_t mapping_table_off; ++ uint32_t padding; ++}; ++ ++struct nmbm_instance { ++ struct nmbm_lower_device lower; ++ ++ uint32_t rawpage_size; ++ uint32_t rawblock_size; ++ uint32_t rawchip_size; ++ ++ uint32_t writesize_mask; ++ uint32_t erasesize_mask; ++ uint16_t writesize_shift; ++ uint16_t erasesize_shift; ++ ++ struct nmbm_signature signature; ++ ++ uint8_t *info_table_cache; ++ uint32_t info_table_size; ++ uint32_t info_table_spare_blocks; ++ struct nmbm_info_table_header info_table; ++ ++ nmbm_bitmap_t *block_state; ++ uint32_t block_state_changed; ++ uint32_t state_table_size; ++ ++ int32_t *block_mapping; ++ uint32_t block_mapping_changed; ++ uint32_t mapping_table_size; ++ ++ uint8_t *page_cache; ++ ++ int protected; ++ ++ uint32_t block_count; ++ uint32_t data_block_count; ++ ++ uint32_t mgmt_start_ba; ++ uint32_t main_table_ba; ++ uint32_t backup_table_ba; ++ uint32_t mapping_blocks_ba; ++ uint32_t mapping_blocks_top_ba; ++ uint32_t signature_ba; ++ ++ enum nmbm_log_category log_display_level; ++}; ++ ++/* Log utilities */ ++#define nlog_debug(ni, fmt, ...) \ ++ nmbm_log(ni, NMBM_LOG_DEBUG, fmt, ##__VA_ARGS__) ++ ++#define nlog_info(ni, fmt, ...) \ ++ nmbm_log(ni, NMBM_LOG_INFO, fmt, ##__VA_ARGS__) ++ ++#define nlog_warn(ni, fmt, ...) \ ++ nmbm_log(ni, NMBM_LOG_WARN, fmt, ##__VA_ARGS__) ++ ++#define nlog_err(ni, fmt, ...) \ ++ nmbm_log(ni, NMBM_LOG_ERR, fmt, ##__VA_ARGS__) ++ ++#define nlog_emerg(ni, fmt, ...) \ ++ nmbm_log(ni, NMBM_LOG_EMERG, fmt, ##__VA_ARGS__) ++ ++#endif /* _NMBM_PRIVATE_H_ */ +--- a/include/nmbm/nmbm-os.h ++++ b/include/nmbm/nmbm-os.h +@@ -0,0 +1,68 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * OS-dependent definitions for NAND Mapped-block Management (NMBM) ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _NMBM_OS_H_ ++#define _NMBM_OS_H_ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static inline uint32_t nmbm_crc32(uint32_t crcval, const void *buf, size_t size) ++{ ++ uint chksz; ++ const unsigned char *p = buf; ++ ++ while (size) { ++ if (size > UINT_MAX) ++ chksz = UINT_MAX; ++ else ++ chksz = (uint)size; ++ ++ crcval = crc32_no_comp(crcval, p, chksz); ++ size -= chksz; ++ p += chksz; ++ } ++ ++ return crcval; ++} ++ ++static inline uint32_t nmbm_lldiv(uint64_t dividend, uint32_t divisor) ++{ ++#if BITS_PER_LONG == 64 ++ return dividend / divisor; ++#else ++ __div64_32(÷nd, divisor); ++ return dividend; ++#endif ++} ++ ++#ifdef CONFIG_NMBM_LOG_LEVEL_DEBUG ++#define NMBM_DEFAULT_LOG_LEVEL 0 ++#elif defined(NMBM_LOG_LEVEL_INFO) ++#define NMBM_DEFAULT_LOG_LEVEL 1 ++#elif defined(NMBM_LOG_LEVEL_WARN) ++#define NMBM_DEFAULT_LOG_LEVEL 2 ++#elif defined(NMBM_LOG_LEVEL_ERR) ++#define NMBM_DEFAULT_LOG_LEVEL 3 ++#elif defined(NMBM_LOG_LEVEL_EMERG) ++#define NMBM_DEFAULT_LOG_LEVEL 4 ++#elif defined(NMBM_LOG_LEVEL_NONE) ++#define NMBM_DEFAULT_LOG_LEVEL 5 ++#else ++#define NMBM_DEFAULT_LOG_LEVEL 1 ++#endif ++ ++#define WATCHDOG_RESET schedule ++ ++#endif /* _NMBM_OS_H_ */ +--- a/include/nmbm/nmbm.h ++++ b/include/nmbm/nmbm.h +@@ -0,0 +1,105 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Definitions for NAND Mapped-block Management (NMBM) ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _NMBM_H_ ++#define _NMBM_H_ ++ ++#include ++ ++enum nmbm_log_category { ++ NMBM_LOG_DEBUG, ++ NMBM_LOG_INFO, ++ NMBM_LOG_WARN, ++ NMBM_LOG_ERR, ++ NMBM_LOG_EMERG, ++ ++ __NMBM_LOG_MAX ++}; ++ ++enum nmbm_oob_mode { ++ NMBM_MODE_PLACE_OOB, ++ NMBM_MODE_AUTO_OOB, ++ NMBM_MODE_RAW, ++ ++ __NMBM_MODE_MAX ++}; ++ ++struct nmbm_lower_device { ++ uint32_t max_ratio; ++ uint32_t max_reserved_blocks; ++ int flags; ++ ++ uint64_t size; ++ uint32_t erasesize; ++ uint32_t writesize; ++ uint32_t oobsize; ++ uint32_t oobavail; ++ ++ void *arg; ++ int (*reset_chip)(void *arg); ++ ++ /* ++ * read_page: ++ * return 0 if succeeds ++ * return positive number for ecc error ++ * return negative number for other errors ++ */ ++ int (*read_page)(void *arg, uint64_t addr, void *buf, void *oob, enum nmbm_oob_mode mode); ++ int (*write_page)(void *arg, uint64_t addr, const void *buf, const void *oob, enum nmbm_oob_mode mode); ++ int (*panic_write_page)(void *arg, uint64_t addr, const void *buf); ++ int (*erase_block)(void *arg, uint64_t addr); ++ ++ int (*is_bad_block)(void *arg, uint64_t addr); ++ int (*mark_bad_block)(void *arg, uint64_t addr); ++ ++ /* OS-dependent logging function */ ++ void (*logprint)(void *arg, enum nmbm_log_category level, const char *fmt, va_list ap); ++}; ++ ++struct nmbm_instance; ++ ++/* Create NMBM if management area not found, or not complete */ ++#define NMBM_F_CREATE 0x01 ++ ++/* Empty page is also protected by ECC, and bitflip(s) can be corrected */ ++#define NMBM_F_EMPTY_PAGE_ECC_OK 0x02 ++ ++/* Do not write anything back to flash */ ++#define NMBM_F_READ_ONLY 0x04 ++ ++size_t nmbm_calc_structure_size(struct nmbm_lower_device *nld); ++int nmbm_attach(struct nmbm_lower_device *nld, struct nmbm_instance *ni); ++int nmbm_detach(struct nmbm_instance *ni); ++ ++enum nmbm_log_category nmbm_set_log_level(struct nmbm_instance *ni, ++ enum nmbm_log_category level); ++ ++int nmbm_erase_block_range(struct nmbm_instance *ni, uint64_t addr, ++ uint64_t size, uint64_t *failed_addr); ++int nmbm_read_single_page(struct nmbm_instance *ni, uint64_t addr, void *data, ++ void *oob, enum nmbm_oob_mode mode); ++int nmbm_read_range(struct nmbm_instance *ni, uint64_t addr, size_t size, ++ void *data, enum nmbm_oob_mode mode, size_t *retlen); ++int nmbm_write_single_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data, const void *oob, ++ enum nmbm_oob_mode mode); ++int nmbm_panic_write_single_page(struct nmbm_instance *ni, uint64_t addr, ++ const void *data); ++int nmbm_write_range(struct nmbm_instance *ni, uint64_t addr, size_t size, ++ const void *data, enum nmbm_oob_mode mode, ++ size_t *retlen); ++ ++int nmbm_check_bad_block(struct nmbm_instance *ni, uint64_t addr); ++int nmbm_mark_bad_block(struct nmbm_instance *ni, uint64_t addr); ++ ++uint64_t nmbm_get_avail_size(struct nmbm_instance *ni); ++ ++int nmbm_get_lower_device(struct nmbm_instance *ni, struct nmbm_lower_device *nld); ++ ++#endif /* _NMBM_H_ */ diff --git a/patch/u-boot/u-boot-filogic/100-07-mtd-nmbm-add-support-for-mtd.patch b/patch/u-boot/u-boot-filogic/100-07-mtd-nmbm-add-support-for-mtd.patch new file mode 100644 index 0000000000..9dea382df2 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-07-mtd-nmbm-add-support-for-mtd.patch @@ -0,0 +1,958 @@ +From 0524995f07fcd216a1a7e267fdb5cf2b0ede8489 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:42:12 +0800 +Subject: [PATCH 41/71] mtd: nmbm: add support for mtd + +Add support to create NMBM based on MTD devices + +Signed-off-by: Weijie Gao +--- + drivers/mtd/nmbm/Kconfig | 5 + + drivers/mtd/nmbm/Makefile | 1 + + drivers/mtd/nmbm/nmbm-mtd.c | 890 ++++++++++++++++++++++++++++++++++++ + include/nmbm/nmbm-mtd.h | 27 ++ + 4 files changed, 923 insertions(+) + create mode 100644 drivers/mtd/nmbm/nmbm-mtd.c + create mode 100644 include/nmbm/nmbm-mtd.h + +--- a/drivers/mtd/nmbm/Kconfig ++++ b/drivers/mtd/nmbm/Kconfig +@@ -27,3 +27,8 @@ config NMBM_LOG_LEVEL_NONE + bool "5 - None" + + endchoice ++ ++config NMBM_MTD ++ bool "Enable MTD based NAND mapping block management" ++ default n ++ depends on NMBM +--- a/drivers/mtd/nmbm/Makefile ++++ b/drivers/mtd/nmbm/Makefile +@@ -3,3 +3,4 @@ + # (C) Copyright 2020 MediaTek Inc. All rights reserved. + + obj-$(CONFIG_NMBM) += nmbm-core.o ++obj-$(CONFIG_NMBM_MTD) += nmbm-mtd.o +--- /dev/null ++++ b/drivers/mtd/nmbm/nmbm-mtd.c +@@ -0,0 +1,890 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "nmbm-debug.h" ++ ++#define NMBM_UPPER_MTD_NAME "nmbm" ++ ++static uint32_t nmbm_id_cnt; ++static LIST_HEAD(nmbm_devs); ++ ++struct nmbm_mtd { ++ struct mtd_info upper; ++ char *name; ++ uint32_t id; ++ ++ struct mtd_info *lower; ++ ++ struct nmbm_instance *ni; ++ uint8_t *page_cache; ++ ++ struct list_head node; ++}; ++ ++static int nmbm_lower_read_page(void *arg, uint64_t addr, void *buf, void *oob, ++ enum nmbm_oob_mode mode) ++{ ++ struct nmbm_mtd *nm = arg; ++ struct mtd_oob_ops ops; ++ int ret; ++ ++ memset(&ops, 0, sizeof(ops)); ++ ++ switch (mode) { ++ case NMBM_MODE_PLACE_OOB: ++ ops.mode = MTD_OPS_PLACE_OOB; ++ break; ++ case NMBM_MODE_AUTO_OOB: ++ ops.mode = MTD_OPS_AUTO_OOB; ++ break; ++ case NMBM_MODE_RAW: ++ ops.mode = MTD_OPS_RAW; ++ break; ++ default: ++ pr_debug("%s: unsupported NMBM mode: %u\n", __func__, mode); ++ return -ENOTSUPP; ++ } ++ ++ if (buf) { ++ ops.datbuf = buf; ++ ops.len = nm->lower->writesize; ++ } ++ ++ if (oob) { ++ ops.oobbuf = oob; ++ ops.ooblen = mtd_oobavail(nm->lower, &ops); ++ } ++ ++ ret = mtd_read_oob(nm->lower, addr, &ops); ++ nm->upper.ecc_stats.corrected = nm->lower->ecc_stats.corrected; ++ nm->upper.ecc_stats.failed = nm->lower->ecc_stats.failed; ++ ++ /* Report error on failure (including ecc error) */ ++ if (ret < 0 && ret != -EUCLEAN) ++ return ret; ++ ++ /* ++ * Since mtd_read_oob() won't report exact bitflips, what we can know ++ * is whether bitflips exceeds the threshold. ++ * We want the -EUCLEAN to be passed to the upper layer, but not the ++ * error value itself. To achieve this, report bitflips above the ++ * threshold. ++ */ ++ ++ if (ret == -EUCLEAN) { ++ return min_t(u32, nm->lower->bitflip_threshold + 1, ++ nm->lower->ecc_strength); ++ } ++ ++ /* For bitflips less than the threshold, return 0 */ ++ ++ return 0; ++} ++ ++static int nmbm_lower_write_page(void *arg, uint64_t addr, const void *buf, ++ const void *oob, enum nmbm_oob_mode mode) ++{ ++ struct nmbm_mtd *nm = arg; ++ struct mtd_oob_ops ops; ++ ++ memset(&ops, 0, sizeof(ops)); ++ ++ switch (mode) { ++ case NMBM_MODE_PLACE_OOB: ++ ops.mode = MTD_OPS_PLACE_OOB; ++ break; ++ case NMBM_MODE_AUTO_OOB: ++ ops.mode = MTD_OPS_AUTO_OOB; ++ break; ++ case NMBM_MODE_RAW: ++ ops.mode = MTD_OPS_RAW; ++ break; ++ default: ++ pr_debug("%s: unsupported NMBM mode: %u\n", __func__, mode); ++ return -ENOTSUPP; ++ } ++ ++ if (buf) { ++ ops.datbuf = (uint8_t *)buf; ++ ops.len = nm->lower->writesize; ++ } ++ ++ if (oob) { ++ ops.oobbuf = (uint8_t *)oob; ++ ops.ooblen = mtd_oobavail(nm->lower, &ops); ++ } ++ ++ return mtd_write_oob(nm->lower, addr, &ops); ++} ++ ++static int nmbm_lower_erase_block(void *arg, uint64_t addr) ++{ ++ struct nmbm_mtd *nm = arg; ++ struct erase_info ei; ++ ++ memset(&ei, 0, sizeof(ei)); ++ ++ ei.mtd = nm->lower; ++ ei.addr = addr; ++ ei.len = nm->lower->erasesize; ++ ++ return mtd_erase(nm->lower, &ei); ++} ++ ++static int nmbm_lower_is_bad_block(void *arg, uint64_t addr) ++{ ++ struct nmbm_mtd *nm = arg; ++ ++ return mtd_block_isbad(nm->lower, addr); ++} ++ ++static int nmbm_lower_mark_bad_block(void *arg, uint64_t addr) ++{ ++ struct nmbm_mtd *nm = arg; ++ ++ return mtd_block_markbad(nm->lower, addr); ++} ++ ++static void nmbm_lower_log(void *arg, enum nmbm_log_category level, ++ const char *fmt, va_list ap) ++{ ++ vprintf(fmt, ap); ++} ++ ++static int nmbm_mtd_read(struct mtd_info *mtd, loff_t from, size_t len, ++ size_t *retlen, u_char *buf) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ ++ /* Do not allow read past end of device */ ++ if ((from + len) > mtd->size) { ++ pr_debug("%s: attempt to write beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ return nmbm_read_range(nm->ni, from, len, buf, MTD_OPS_PLACE_OOB, ++ retlen); ++} ++ ++static int nmbm_mtd_write(struct mtd_info *mtd, loff_t to, size_t len, ++ size_t *retlen, const u_char *buf) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ ++ /* Do not allow write past end of device */ ++ if ((to + len) > mtd->size) { ++ pr_debug("%s: attempt to write beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ return nmbm_write_range(nm->ni, to, len, buf, MTD_OPS_PLACE_OOB, ++ retlen); ++} ++ ++static int nmbm_mtd_erase(struct mtd_info *mtd, struct erase_info *instr) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ int ret; ++ ++ instr->state = MTD_ERASING; ++ ++ ret = nmbm_erase_block_range(nm->ni, instr->addr, instr->len, ++ &instr->fail_addr); ++ if (ret) ++ instr->state = MTD_ERASE_FAILED; ++ else ++ instr->state = MTD_ERASE_DONE; ++ ++ if (!ret) ++ /* FIXME */ ++ /* mtd_erase_callback(instr); */ ++ return ret; ++ else ++ ret = -EIO; ++ ++ return ret; ++} ++ ++static int nmbm_mtd_read_data(struct nmbm_mtd *nm, uint64_t addr, ++ struct mtd_oob_ops *ops, enum nmbm_oob_mode mode) ++{ ++ size_t len, ooblen, maxooblen, chklen; ++ uint32_t col, ooboffs; ++ uint8_t *datcache, *oobcache; ++ bool has_ecc_err = false; ++ int ret, max_bitflips = 0; ++ ++ col = addr & nm->lower->writesize_mask; ++ addr &= ~nm->lower->writesize_mask; ++ maxooblen = mtd_oobavail(nm->lower, ops); ++ ooboffs = ops->ooboffs; ++ ooblen = ops->ooblen; ++ len = ops->len; ++ ++ datcache = len ? nm->page_cache : NULL; ++ oobcache = ooblen ? nm->page_cache + nm->lower->writesize : NULL; ++ ++ ops->oobretlen = 0; ++ ops->retlen = 0; ++ ++ while (len || ooblen) { ++ schedule(); ++ ++ ret = nmbm_read_single_page(nm->ni, addr, datcache, oobcache, ++ mode); ++ if (ret < 0 && ret != -EBADMSG) ++ return ret; ++ ++ /* Continue reading on ecc error */ ++ if (ret == -EBADMSG) ++ has_ecc_err = true; ++ ++ /* Record the maximum bitflips between pages */ ++ if (ret > max_bitflips) ++ max_bitflips = ret; ++ ++ if (len) { ++ /* Move data */ ++ chklen = nm->lower->writesize - col; ++ if (chklen > len) ++ chklen = len; ++ ++ memcpy(ops->datbuf + ops->retlen, datcache + col, ++ chklen); ++ len -= chklen; ++ col = 0; /* (col + chklen) % */ ++ ops->retlen += chklen; ++ } ++ ++ if (ooblen) { ++ /* Move oob */ ++ chklen = maxooblen - ooboffs; ++ if (chklen > ooblen) ++ chklen = ooblen; ++ ++ memcpy(ops->oobbuf + ops->oobretlen, oobcache + ooboffs, ++ chklen); ++ ooblen -= chklen; ++ ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ ++ ops->oobretlen += chklen; ++ } ++ ++ addr += nm->lower->writesize; ++ } ++ ++ if (has_ecc_err) ++ return -EBADMSG; ++ ++ return max_bitflips; ++} ++ ++static int nmbm_mtd_read_oob(struct mtd_info *mtd, loff_t from, ++ struct mtd_oob_ops *ops) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ uint32_t maxooblen; ++ enum nmbm_oob_mode mode; ++ ++ if (!ops->oobbuf && !ops->datbuf) { ++ if (ops->ooblen || ops->len) ++ return -EINVAL; ++ ++ return 0; ++ } ++ ++ switch (ops->mode) { ++ case MTD_OPS_PLACE_OOB: ++ mode = NMBM_MODE_PLACE_OOB; ++ break; ++ case MTD_OPS_AUTO_OOB: ++ mode = NMBM_MODE_AUTO_OOB; ++ break; ++ case MTD_OPS_RAW: ++ mode = NMBM_MODE_RAW; ++ break; ++ default: ++ pr_debug("%s: unsupported oob mode: %u\n", __func__, ops->mode); ++ return -ENOTSUPP; ++ } ++ ++ maxooblen = mtd_oobavail(mtd, ops); ++ ++ /* Do not allow read past end of device */ ++ if (ops->datbuf && (from + ops->len) > mtd->size) { ++ pr_debug("%s: attempt to read beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (!ops->oobbuf) { ++ /* Optimized for reading data only */ ++ return nmbm_read_range(nm->ni, from, ops->len, ops->datbuf, ++ mode, &ops->retlen); ++ } ++ ++ if (unlikely(ops->ooboffs >= maxooblen)) { ++ pr_debug("%s: attempt to start read outside oob\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (unlikely(from >= mtd->size || ++ ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - ++ (from >> mtd->writesize_shift)) * maxooblen)) { ++ pr_debug("%s: attempt to read beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ return nmbm_mtd_read_data(nm, from, ops, mode); ++} ++ ++static int nmbm_mtd_write_data(struct nmbm_mtd *nm, uint64_t addr, ++ struct mtd_oob_ops *ops, enum nmbm_oob_mode mode) ++{ ++ size_t len, ooblen, maxooblen, chklen; ++ uint32_t col, ooboffs; ++ uint8_t *datcache, *oobcache; ++ int ret; ++ ++ col = addr & nm->lower->writesize_mask; ++ addr &= ~nm->lower->writesize_mask; ++ maxooblen = mtd_oobavail(nm->lower, ops); ++ ooboffs = ops->ooboffs; ++ ooblen = ops->ooblen; ++ len = ops->len; ++ ++ datcache = len ? nm->page_cache : NULL; ++ oobcache = ooblen ? nm->page_cache + nm->lower->writesize : NULL; ++ ++ ops->oobretlen = 0; ++ ops->retlen = 0; ++ ++ while (len || ooblen) { ++ schedule(); ++ ++ if (len) { ++ /* Move data */ ++ chklen = nm->lower->writesize - col; ++ if (chklen > len) ++ chklen = len; ++ ++ memset(datcache, 0xff, col); ++ memcpy(datcache + col, ops->datbuf + ops->retlen, ++ chklen); ++ memset(datcache + col + chklen, 0xff, ++ nm->lower->writesize - col - chklen); ++ len -= chklen; ++ col = 0; /* (col + chklen) % */ ++ ops->retlen += chklen; ++ } ++ ++ if (ooblen) { ++ /* Move oob */ ++ chklen = maxooblen - ooboffs; ++ if (chklen > ooblen) ++ chklen = ooblen; ++ ++ memset(oobcache, 0xff, ooboffs); ++ memcpy(oobcache + ooboffs, ++ ops->oobbuf + ops->oobretlen, chklen); ++ memset(oobcache + ooboffs + chklen, 0xff, ++ nm->lower->oobsize - ooboffs - chklen); ++ ooblen -= chklen; ++ ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */ ++ ops->oobretlen += chklen; ++ } ++ ++ ret = nmbm_write_single_page(nm->ni, addr, datcache, oobcache, ++ mode); ++ if (ret) ++ return ret; ++ ++ addr += nm->lower->writesize; ++ } ++ ++ return 0; ++} ++ ++static int nmbm_mtd_write_oob(struct mtd_info *mtd, loff_t to, ++ struct mtd_oob_ops *ops) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ enum nmbm_oob_mode mode; ++ uint32_t maxooblen; ++ ++ if (!ops->oobbuf && !ops->datbuf) { ++ if (ops->ooblen || ops->len) ++ return -EINVAL; ++ ++ return 0; ++ } ++ ++ switch (ops->mode) { ++ case MTD_OPS_PLACE_OOB: ++ mode = NMBM_MODE_PLACE_OOB; ++ break; ++ case MTD_OPS_AUTO_OOB: ++ mode = NMBM_MODE_AUTO_OOB; ++ break; ++ case MTD_OPS_RAW: ++ mode = NMBM_MODE_RAW; ++ break; ++ default: ++ pr_debug("%s: unsupported oob mode: %u\n", __func__, ++ ops->mode); ++ return -ENOTSUPP; ++ } ++ ++ maxooblen = mtd_oobavail(mtd, ops); ++ ++ /* Do not allow write past end of device */ ++ if (ops->datbuf && (to + ops->len) > mtd->size) { ++ pr_debug("%s: attempt to write beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (!ops->oobbuf) { ++ /* Optimized for writing data only */ ++ return nmbm_write_range(nm->ni, to, ops->len, ops->datbuf, ++ mode, &ops->retlen); ++ } ++ ++ if (unlikely(ops->ooboffs >= maxooblen)) { ++ pr_debug("%s: attempt to start write outside oob\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ if (unlikely(to >= mtd->size || ++ ops->ooboffs + ops->ooblen > ((mtd->size >> mtd->writesize_shift) - ++ (to >> mtd->writesize_shift)) * maxooblen)) { ++ pr_debug("%s: attempt to write beyond end of device\n", ++ __func__); ++ return -EINVAL; ++ } ++ ++ return nmbm_mtd_write_data(nm, to, ops, mode); ++} ++ ++static int nmbm_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ ++ return nmbm_check_bad_block(nm->ni, offs); ++} ++ ++static int nmbm_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) ++{ ++ struct nmbm_mtd *nm = container_of(mtd, struct nmbm_mtd, upper); ++ ++ return nmbm_mark_bad_block(nm->ni, offs); ++} ++ ++int nmbm_attach_mtd(struct mtd_info *lower, int flags, uint32_t max_ratio, ++ uint32_t max_reserved_blocks, struct mtd_info **upper) ++{ ++ struct nmbm_lower_device nld; ++ struct nmbm_instance *ni; ++ struct mtd_info *mtd; ++ struct nmbm_mtd *nm; ++ size_t namelen, alloc_size; ++ int ret; ++ ++ if (!lower) ++ return -EINVAL; ++ ++ if (lower->type != MTD_NANDFLASH || lower->flags != MTD_CAP_NANDFLASH) ++ return -ENOTSUPP; ++ ++ namelen = strlen(NMBM_UPPER_MTD_NAME) + 16; ++ ++ nm = calloc(sizeof(*nm) + lower->writesize + lower->oobsize + namelen + 1, 1); ++ if (!nm) ++ return -ENOMEM; ++ ++ nm->lower = lower; ++ nm->name = (char *)nm + sizeof(*nm); ++ nm->page_cache = (uint8_t *)nm->name + namelen + 1; ++ ++ nm->id = nmbm_id_cnt++; ++ snprintf(nm->name, namelen + 1, "%s%u", NMBM_UPPER_MTD_NAME, nm->id); ++ ++ memset(&nld, 0, sizeof(nld)); ++ ++ nld.flags = flags; ++ nld.max_ratio = max_ratio; ++ nld.max_reserved_blocks = max_reserved_blocks; ++ ++ nld.size = lower->size; ++ nld.erasesize = lower->erasesize; ++ nld.writesize = lower->writesize; ++ nld.oobsize = lower->oobsize; ++ nld.oobavail = lower->oobavail; ++ ++ nld.arg = nm; ++ nld.read_page = nmbm_lower_read_page; ++ nld.write_page = nmbm_lower_write_page; ++ nld.erase_block = nmbm_lower_erase_block; ++ nld.is_bad_block = nmbm_lower_is_bad_block; ++ nld.mark_bad_block = nmbm_lower_mark_bad_block; ++ ++ nld.logprint = nmbm_lower_log; ++ ++ alloc_size = nmbm_calc_structure_size(&nld); ++ ni = calloc(alloc_size, 1); ++ if (!ni) { ++ free(nm); ++ return -ENOMEM; ++ } ++ ++ ret = nmbm_attach(&nld, ni); ++ if (ret) { ++ free(ni); ++ free(nm); ++ return ret; ++ } ++ ++ nm->ni = ni; ++ ++ /* Initialize upper mtd */ ++ mtd = &nm->upper; ++ ++ mtd->name = nm->name; ++ mtd->type = MTD_DEV_TYPE_NMBM; ++ mtd->flags = lower->flags; ++ ++ mtd->size = (uint64_t)ni->data_block_count * ni->lower.erasesize; ++ mtd->erasesize = lower->erasesize; ++ mtd->writesize = lower->writesize; ++ mtd->writebufsize = lower->writesize; ++ mtd->oobsize = lower->oobsize; ++ mtd->oobavail = lower->oobavail; ++ ++ mtd->erasesize_shift = lower->erasesize_shift; ++ mtd->writesize_shift = lower->writesize_shift; ++ mtd->erasesize_mask = lower->erasesize_mask; ++ mtd->writesize_mask = lower->writesize_mask; ++ ++ mtd->bitflip_threshold = lower->bitflip_threshold; ++ ++ /* XXX: should this be duplicated? */ ++ mtd->ooblayout = lower->ooblayout; ++ mtd->ecclayout = lower->ecclayout; ++ ++ mtd->ecc_step_size = lower->ecc_step_size; ++ mtd->ecc_strength = lower->ecc_strength; ++ ++ mtd->numeraseregions = lower->numeraseregions; ++ mtd->eraseregions = lower->eraseregions; ++ ++ mtd->_read = nmbm_mtd_read; ++ mtd->_write = nmbm_mtd_write; ++ mtd->_erase = nmbm_mtd_erase; ++ mtd->_read_oob = nmbm_mtd_read_oob; ++ mtd->_write_oob = nmbm_mtd_write_oob; ++ mtd->_block_isbad = nmbm_mtd_block_isbad; ++ mtd->_block_markbad = nmbm_mtd_block_markbad; ++ ++ *upper = mtd; ++ ++ list_add_tail(&nm->node, &nmbm_devs); ++ ++ return 0; ++} ++ ++int nmbm_free_mtd(struct mtd_info *upper) ++{ ++ struct nmbm_mtd *pos; ++ ++ if (!upper) ++ return -EINVAL; ++ ++ list_for_each_entry(pos, &nmbm_devs, node) { ++ if (&pos->upper == upper) { ++ list_del(&pos->node); ++ ++ nmbm_detach(pos->ni); ++ free(pos->ni); ++ free(pos); ++ ++ return 0; ++ } ++ } ++ ++ return -ENODEV; ++} ++ ++struct mtd_info *nmbm_mtd_get_upper_by_index(uint32_t index) ++{ ++ struct nmbm_mtd *nm; ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ if (nm->id == index) ++ return &nm->upper; ++ } ++ ++ return NULL; ++} ++ ++struct mtd_info *nmbm_mtd_get_upper(struct mtd_info *lower) ++{ ++ struct nmbm_mtd *nm; ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ if (nm->lower == lower) ++ return &nm->upper; ++ } ++ ++ return NULL; ++} ++ ++void nmbm_mtd_list_devices(void) ++{ ++ struct nmbm_mtd *nm; ++ ++ printf("Index NMBM device Lower device\n"); ++ printf("========================================\n"); ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ printf("%-8u%-20s%s\n", nm->id, nm->name, nm->lower->name); ++ } ++} ++ ++int nmbm_mtd_print_info(const char *name) ++{ ++ struct nmbm_mtd *nm; ++ bool found = false; ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ if (!strcmp(nm->name, name)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) { ++ printf("Error: NMBM device '%s' not found\n", name); ++ return -ENODEV; ++ } ++ ++ printf("%s:\n", name); ++ printf("Total blocks: %u\n", nm->ni->block_count); ++ printf("Data blocks: %u\n", nm->ni->data_block_count); ++ printf("Management start block: %u\n", nm->ni->mgmt_start_ba); ++ printf("Info table size: 0x%x\n", nm->ni->info_table_size); ++ ++ if (nm->ni->main_table_ba) ++ printf("Main info table start block: %u\n", nm->ni->main_table_ba); ++ else ++ printf("Main info table start block: Not exist\n"); ++ ++ if (nm->ni->backup_table_ba) ++ printf("Backup info table start block: %u\n", nm->ni->backup_table_ba); ++ else ++ printf("Backup info table start block: Not exist\n"); ++ ++ printf("Signature block: %u\n", nm->ni->signature_ba); ++ printf("Mapping blocks top address: %u\n", nm->ni->mapping_blocks_top_ba); ++ printf("Mapping blocks limit address: %u\n", nm->ni->mapping_blocks_ba); ++ ++ return 0; ++} ++ ++static const char nmbm_block_legends[] = { ++ [NMBM_BLOCK_GOOD_DATA] = '-', ++ [NMBM_BLOCK_GOOD_MGMT] = '+', ++ [NMBM_BLOCK_BAD] = 'B', ++ [NMBM_BLOCK_MAIN_INFO_TABLE] = 'I', ++ [NMBM_BLOCK_BACKUP_INFO_TABLE] = 'i', ++ [NMBM_BLOCK_REMAPPED] = 'M', ++ [NMBM_BLOCK_SIGNATURE] = 'S', ++}; ++ ++int nmbm_mtd_print_states(const char *name) ++{ ++ struct nmbm_mtd *nm; ++ enum nmmb_block_type bt; ++ bool found = false; ++ uint32_t i; ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ if (!strcmp(nm->name, name)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) { ++ printf("Error: NMBM device '%s' not found\n", name); ++ return -ENODEV; ++ } ++ ++ printf("Physical blocks:\n"); ++ printf("\n"); ++ ++ printf("Legends:\n"); ++ printf(" - Good data block\n"); ++ printf(" + Good management block\n"); ++ printf(" B Bad block\n"); ++ printf(" I Main info table\n"); ++ printf(" i Backup info table\n"); ++ printf(" M Remapped spare block\n"); ++ printf(" S Signature block\n"); ++ printf("\n"); ++ ++ for (i = 0; i < nm->ni->block_count; i++) { ++ if (i % 64 == 0) ++ printf(" "); ++ ++ bt = nmbm_debug_get_phys_block_type(nm->ni, i); ++ if (bt < __NMBM_BLOCK_TYPE_MAX) ++ putc(nmbm_block_legends[bt]); ++ else ++ putc('?'); ++ ++ if (i % 64 == 63) ++ printf("\n"); ++ } ++ ++ printf("\n"); ++ printf("Logical blocks:\n"); ++ printf("\n"); ++ ++ printf("Legends:\n"); ++ printf(" - Good block\n"); ++ printf(" + Initially remapped block\n"); ++ printf(" M Remapped block\n"); ++ printf(" B Bad/Unmapped block\n"); ++ printf("\n"); ++ ++ for (i = 0; i < nm->ni->data_block_count; i++) { ++ if (i % 64 == 0) ++ printf(" "); ++ ++ if (nm->ni->block_mapping[i] < 0) ++ putc('B'); ++ else if (nm->ni->block_mapping[i] == i) ++ putc('-'); ++ else if (nm->ni->block_mapping[i] < nm->ni->data_block_count) ++ putc('+'); ++ else if (nm->ni->block_mapping[i] > nm->ni->mapping_blocks_top_ba && ++ nm->ni->block_mapping[i] < nm->ni->signature_ba) ++ putc('M'); ++ else ++ putc('?'); ++ ++ if (i % 64 == 63) ++ printf("\n"); ++ } ++ ++ return 0; ++} ++ ++int nmbm_mtd_print_bad_blocks(const char *name) ++{ ++ struct nmbm_mtd *nm; ++ bool found = false; ++ uint32_t i; ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ if (!strcmp(nm->name, name)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) { ++ printf("Error: NMBM device '%s' not found\n", name); ++ return -ENODEV; ++ } ++ ++ printf("Physical blocks:\n"); ++ ++ for (i = 0; i < nm->ni->block_count; i++) { ++ switch (nmbm_debug_get_block_state(nm->ni, i)) { ++ case BLOCK_ST_BAD: ++ printf("%-12u [0x%08llx] - Bad\n", i, ++ (uint64_t)i << nm->ni->erasesize_shift); ++ break; ++ case BLOCK_ST_NEED_REMAP: ++ printf("%-12u [0x%08llx] - Awaiting remapping\n", i, ++ (uint64_t)i << nm->ni->erasesize_shift); ++ break; ++ } ++ } ++ ++ printf("\n"); ++ printf("Logical blocks:\n"); ++ ++ for (i = 0; i < nm->ni->data_block_count; i++) { ++ if (nm->ni->block_mapping[i] < 0) { ++ printf("%-12u [0x%08llx] - Bad\n", i, ++ (uint64_t)i << nm->ni->erasesize_shift); ++ } ++ } ++ ++ return 0; ++} ++ ++int nmbm_mtd_print_mappings(const char *name, int printall) ++{ ++ struct nmbm_mtd *nm; ++ bool found = false; ++ int32_t pb; ++ uint32_t i; ++ ++ list_for_each_entry(nm, &nmbm_devs, node) { ++ if (!strcmp(nm->name, name)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (!found) { ++ printf("Error: NMBM device '%s' not found\n", name); ++ return -ENODEV; ++ } ++ ++ printf("Logical Block Physical Block\n"); ++ printf("==================================\n"); ++ ++ if (!printall) { ++ for (i = 0; i < nm->ni->data_block_count; i++) { ++ pb = nm->ni->block_mapping[i]; ++ if (pb < 0) ++ printf("%-20uUnmapped\n", i); ++ else if ((uint32_t)pb > nm->ni->mapping_blocks_top_ba && ++ (uint32_t)pb < nm->ni->signature_ba) ++ printf("%-20u%u\n", i, pb); ++ } ++ ++ return 0; ++ } ++ ++ for (i = 0; i < nm->ni->data_block_count; i++) { ++ pb = nm->ni->block_mapping[i]; ++ ++ if (pb >= 0) ++ printf("%-20u%u\n", i, pb); ++ else ++ printf("%-20uUnmapped\n", i); ++ } ++ ++ return 0; ++} +--- a/include/nmbm/nmbm-mtd.h ++++ b/include/nmbm/nmbm-mtd.h +@@ -0,0 +1,27 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#ifndef _NMBM_MTD_H_ ++#define _NMBM_MTD_H_ ++ ++#include ++ ++int nmbm_attach_mtd(struct mtd_info *lower, int flags, uint32_t max_ratio, ++ uint32_t max_reserved_blocks, struct mtd_info **upper); ++ ++int nmbm_free_mtd(struct mtd_info *upper); ++ ++struct mtd_info *nmbm_mtd_get_upper_by_index(uint32_t index); ++struct mtd_info *nmbm_mtd_get_upper(struct mtd_info *lower); ++ ++void nmbm_mtd_list_devices(void); ++int nmbm_mtd_print_info(const char *name); ++int nmbm_mtd_print_states(const char *name); ++int nmbm_mtd_print_bad_blocks(const char *name); ++int nmbm_mtd_print_mappings(const char *name, int printall); ++ ++#endif /* _NMBM_MTD_H_ */ diff --git a/patch/u-boot/u-boot-filogic/100-08-common-board_r-add-support-to-initialize-NMBM-after-.patch b/patch/u-boot/u-boot-filogic/100-08-common-board_r-add-support-to-initialize-NMBM-after-.patch new file mode 100644 index 0000000000..381ceb29de --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-08-common-board_r-add-support-to-initialize-NMBM-after-.patch @@ -0,0 +1,46 @@ +From dcf24c8deeb43a4406ae18136c8700dc2f867415 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 11:18:03 +0800 +Subject: [PATCH 42/71] common: board_r: add support to initialize NMBM after + nand initialization + +This patch add support to initialize NMBM after nand initialized. + +Signed-off-by: Weijie Gao +--- + common/board_r.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +--- a/common/board_r.c ++++ b/common/board_r.c +@@ -378,6 +378,20 @@ static int initr_nand(void) + } + #endif + ++#ifdef CONFIG_NMBM_MTD ++ ++__weak int board_nmbm_init(void) ++{ ++ return 0; ++} ++ ++/* go init the NMBM */ ++static int initr_nmbm(void) ++{ ++ return board_nmbm_init(); ++} ++#endif ++ + #if defined(CONFIG_CMD_ONENAND) + /* go init the NAND */ + static int initr_onenand(void) +@@ -693,6 +707,9 @@ static init_fnc_t init_sequence_r[] = { + #ifdef CONFIG_CMD_ONENAND + initr_onenand, + #endif ++#ifdef CONFIG_NMBM_MTD ++ initr_nmbm, ++#endif + #ifdef CONFIG_MMC + initr_mmc, + #endif diff --git a/patch/u-boot/u-boot-filogic/100-09-cmd-add-nmbm-command.patch b/patch/u-boot/u-boot-filogic/100-09-cmd-add-nmbm-command.patch new file mode 100644 index 0000000000..6d89c7480e --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-09-cmd-add-nmbm-command.patch @@ -0,0 +1,370 @@ +From 0af8d0aac77f4df4bc7dadbcdea5d9a16f5f3e45 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:44:57 +0800 +Subject: [PATCH 43/71] cmd: add nmbm command + +Add nmbm command for debugging, data operations and image-booting support + +Signed-off-by: Weijie Gao +--- + cmd/Kconfig | 6 + + cmd/Makefile | 1 + + cmd/nmbm.c | 327 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 334 insertions(+) + create mode 100644 cmd/nmbm.c + +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -1525,6 +1525,12 @@ config CMD_NAND_WATCH + + endif # CMD_NAND + ++config CMD_NMBM ++ depends on NMBM_MTD ++ bool "nmbm" ++ help ++ NAND mapping block management (NMBM) utility ++ + config CMD_NVME + bool "nvme" + depends on NVME +--- a/cmd/Makefile ++++ b/cmd/Makefile +@@ -130,6 +130,7 @@ obj-y += legacy-mtd-utils.o + endif + obj-$(CONFIG_CMD_MUX) += mux.o + obj-$(CONFIG_CMD_NAND) += nand.o ++obj-$(CONFIG_CMD_NMBM) += nmbm.o + ifdef CONFIG_NET + obj-$(CONFIG_CMD_NET) += net.o net-common.o + else ifdef CONFIG_NET_LWIP +--- /dev/null ++++ b/cmd/nmbm.c +@@ -0,0 +1,327 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++static int nmbm_parse_offset_size(struct mtd_info *mtd, char *off_str, ++ char *size_str, uint64_t *off, ++ uint64_t *size) ++{ ++ char *end; ++ ++ *off = simple_strtoull(off_str, &end, 16); ++ if (end == off_str) { ++ printf("Error: offset '%s' is invalid\n", off_str); ++ return -EINVAL; ++ } ++ ++ if (*off >= mtd->size) { ++ printf("Error: offset '0x%llx' is beyond the end of device\n", ++ *off); ++ return -EINVAL; ++ } ++ ++ *size = simple_strtoull(size_str, &end, 16); ++ if (end == off_str) { ++ printf("Error: size '%s' is invalid\n", off_str); ++ return -EINVAL; ++ } ++ ++ if (*off + *size > mtd->size) { ++ printf("Error: size '0x%llx' is too large\n", *size); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int do_nmbm_erase(struct mtd_info *mtd, uint64_t offset, uint64_t size) ++{ ++ struct erase_info ei; ++ int ret; ++ ++ memset(&ei, 0, sizeof(ei)); ++ ++ ei.mtd = mtd; ++ ei.addr = offset; ++ ei.len = size; ++ ++ printf("Erasing from 0x%llx, size 0x%llx ...\n", offset, size); ++ ++ ret = mtd_erase(mtd, &ei); ++ ++ if (!ret) { ++ printf("Succeeded\n"); ++ return CMD_RET_SUCCESS; ++ } ++ ++ printf("Failed at 0x%llx\n", ei.fail_addr); ++ ++ return CMD_RET_FAILURE; ++} ++ ++static int do_nmbm_rw(int read, struct mtd_info *mtd, uintptr_t addr, ++ uint64_t offset, size_t size) ++{ ++ size_t retlen; ++ int ret; ++ ++ printf("%s 0x%llx, size 0x%zx\n", read ? "Reading from" : "Writing to", ++ offset, size); ++ ++ if (read) ++ ret = mtd_read(mtd, offset, size, &retlen, (void *)addr); ++ else ++ ret = mtd_write(mtd, offset, size, &retlen, (void *)addr); ++ ++ if (!ret) { ++ printf("Succeeded\n"); ++ return CMD_RET_SUCCESS; ++ } ++ ++ printf("Failed at 0x%llx\n", offset + retlen); ++ ++ return CMD_RET_FAILURE; ++} ++ ++static int do_nmbm_mtd_boot(struct cmd_tbl *cmdtp, struct mtd_info *mtd, ++ int argc, char *const argv[]) ++{ ++ bool print_image_contents = true; ++ uintptr_t loadaddr = image_load_addr; ++ char *end, *image_name; ++ const char *ep; ++ size_t retlen; ++ uint32_t size; ++ uint64_t off; ++ int ret; ++ ++#if defined(CONFIG_CMD_MTDPARTS) ++ struct mtd_device *partdev; ++ struct mtd_info *partmtd; ++ struct part_info *part; ++ u8 pnum; ++#endif ++ ++ ep = env_get("autostart"); ++ ++ if (ep && !strcmp(ep, "yes")) ++ print_image_contents = false; ++ ++ if (argc == 2) { ++ loadaddr = simple_strtoul(argv[0], &end, 0); ++ if (*end || end == argv[0]) { ++ printf("'%s' is not a valid address\n", argv[0]); ++ return CMD_RET_FAILURE; ++ } ++ ++ argc--; ++ argv++; ++ } ++ ++ off = simple_strtoull(argv[0], &end, 0); ++ if (*end || end == argv[0]) { ++#if defined(CONFIG_CMD_MTDPARTS) ++ ret = mtdparts_init(); ++ if (ret) ++ return CMD_RET_FAILURE; ++ ++ ret = find_dev_and_part(argv[0], &partdev, &pnum, &part); ++ if (ret) ++ return CMD_RET_FAILURE; ++ ++ if (partdev->id->type != MTD_DEV_TYPE_NMBM) { ++ printf("'%s' is not a NMBM device partition\n", ++ argv[0]); ++ return CMD_RET_FAILURE; ++ } ++ ++ partmtd = nmbm_mtd_get_upper_by_index(partdev->id->num); ++ ++ if (partmtd != mtd) { ++ printf("'%s' does not belong to this device\n", ++ argv[0]); ++ return CMD_RET_FAILURE; ++ } ++ ++ off = part->offset; ++#else ++ printf("'%s' is not a valid offset\n", argv[0]); ++ return CMD_RET_FAILURE; ++#endif ++ } ++ ++ ret = mtd_read(mtd, off, sizeof(struct legacy_img_hdr), &retlen, ++ (void *)loadaddr); ++ if (ret || retlen != sizeof(struct legacy_img_hdr)) { ++ printf("Failed to read NMBM at offset 0x%08llx\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ switch (genimg_get_format((void *)loadaddr)) { ++#if defined(CONFIG_LEGACY_IMAGE_FORMAT) ++ case IMAGE_FORMAT_LEGACY: ++ size = image_get_image_size((struct legacy_img_hdr *)loadaddr); ++ image_name = "legacy"; ++ break; ++#endif ++#if defined(CONFIG_FIT) ++ case IMAGE_FORMAT_FIT: ++ size = fit_get_size((const void *)loadaddr); ++ image_name = "FIT"; ++ break; ++#endif ++ default: ++ printf("Error: no Image found at offset 0x%08llx\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ printf("Loading %s image at offset 0x%llx to memory 0x%08lx, size 0x%x ...\n", ++ image_name, off, loadaddr, size); ++ ++ ret = mtd_read(mtd, off, size, &retlen, (void *)loadaddr); ++ if (ret || retlen != size) { ++ printf("Error: Failed to load image at offset 0x%08llx\n", ++ off + retlen); ++ return CMD_RET_FAILURE; ++ } ++ ++ switch (genimg_get_format((void *)loadaddr)) { ++#if defined(CONFIG_LEGACY_IMAGE_FORMAT) ++ case IMAGE_FORMAT_LEGACY: ++ if (print_image_contents) ++ image_print_contents((void *)loadaddr); ++ break; ++#endif ++#if defined(CONFIG_FIT) ++ case IMAGE_FORMAT_FIT: ++ if (print_image_contents) ++ fit_print_contents((void *)loadaddr); ++ break; ++#endif ++ } ++ ++ image_load_addr = loadaddr; ++ ++ return bootm_maybe_autostart(cmdtp, "nmbm"); ++} ++ ++static int do_nmbm(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd; ++ uint64_t offset, size; ++ char *end; ++ uintptr_t addr; ++ int ret, all = 0; ++ ++ if (argc == 1) ++ return CMD_RET_USAGE; ++ ++ if (!strcmp(argv[1], "list")) { ++ nmbm_mtd_list_devices(); ++ return CMD_RET_SUCCESS; ++ } ++ ++ if (argc < 3) ++ return CMD_RET_USAGE; ++ ++ if (!strcmp(argv[2], "info")) ++ return !!nmbm_mtd_print_info(argv[1]); ++ ++ if (!strcmp(argv[2], "state")) ++ return !!nmbm_mtd_print_states(argv[1]); ++ ++ if (!strcmp(argv[2], "bad")) ++ return !!nmbm_mtd_print_bad_blocks(argv[1]); ++ ++ if (!strcmp(argv[2], "mapping")) { ++ if (argc >= 4) { ++ if (!strcmp(argv[3], "all")) ++ all = 1; ++ } ++ ++ return nmbm_mtd_print_mappings(argv[1], all); ++ } ++ ++ if (argc < 4) ++ return CMD_RET_USAGE; ++ ++ mtd = get_mtd_device_nm(argv[1]); ++ if (IS_ERR(mtd)) { ++ printf("Error: NMBM device '%s' not found\n", argv[1]); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (mtd->type != MTD_DEV_TYPE_NMBM) { ++ printf("Error: '%s' is not a NMBM device\n", argv[1]); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (!strcmp(argv[2], "boot")) ++ return do_nmbm_mtd_boot(cmdtp, mtd, argc - 3, argv + 3); ++ ++ if (argc < 5) ++ return CMD_RET_USAGE; ++ ++ if (!strcmp(argv[2], "erase")) { ++ ret = nmbm_parse_offset_size(mtd, argv[3], argv[4], &offset, ++ &size); ++ if (ret) ++ return CMD_RET_FAILURE; ++ ++ return do_nmbm_erase(mtd, offset, size); ++ } ++ ++ if (argc < 6) ++ return CMD_RET_USAGE; ++ ++ ret = nmbm_parse_offset_size(mtd, argv[4], argv[5], &offset, &size); ++ if (ret) ++ return CMD_RET_FAILURE; ++ ++ if (size > SIZE_MAX) { ++ printf("Error: size 0x%llx is too large\n", size); ++ return -EINVAL; ++ } ++ ++ addr = simple_strtoul(argv[3], &end, 16); ++ if (end == argv[3]) { ++ printf("Error: addr '%s' is invalid\n", argv[3]); ++ return -EINVAL; ++ } ++ ++ if (!strcmp(argv[2], "read")) ++ return do_nmbm_rw(1, mtd, addr, offset, (size_t)size); ++ ++ if (!strcmp(argv[2], "write")) ++ return do_nmbm_rw(0, mtd, addr, offset, (size_t)size); ++ ++ return CMD_RET_USAGE; ++} ++ ++U_BOOT_CMD( ++ nmbm, CONFIG_SYS_MAXARGS, 0, do_nmbm, ++ "NMBM utility commands", ++ "\n" ++ "nmbm list - List NMBM devices\n" ++ "nmbm info - Display NMBM information\n" ++ "nmbm state - Display block states\n" ++ "nmbm bad - Display bad blocks\n" ++ "nmbm boot - Boot from NMBM\n" ++ "nmbm mapping [all] - Display block mapping\n" ++ "nmbm erase - Erase blocks\n" ++ "nmbm read - Read data\n" ++ "nmbm write - Write data\n" ++); diff --git a/patch/u-boot/u-boot-filogic/100-10-cmd-mtd-add-markbad-subcommand-for-NMBM-testing.patch b/patch/u-boot/u-boot-filogic/100-10-cmd-mtd-add-markbad-subcommand-for-NMBM-testing.patch new file mode 100644 index 0000000000..a772b485f4 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-10-cmd-mtd-add-markbad-subcommand-for-NMBM-testing.patch @@ -0,0 +1,80 @@ +From 6dbbc8affb6ab22f940d13d0e928d5e881127ca4 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 11:22:57 +0800 +Subject: [PATCH 44/71] cmd: mtd: add markbad subcommand for NMBM testing + +This patch adds: +* Mark bad block on lower mtd device and erase on upper mtd +device, which will trigger remapping: +$ mtd markbad spi-nand0 0x20000 (mark block1 as bad) +$ mtd erase nmbm0 0x20000 0x20000 (let nmbm detect the bad block and remap it) + +* Clear bad block mark through: +$ mtd erase.dontskipbad spi-nand0 0x20000 0x20000 +(After cleaning bad block mark, we need to rebuild nmbm manage table.) + +Signed-off-by: SkyLake.Huang +--- + cmd/mtd.c | 39 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 39 insertions(+) + +--- a/cmd/mtd.c ++++ b/cmd/mtd.c +@@ -728,6 +728,42 @@ out_put_mtd: + return CMD_RET_SUCCESS; + } + ++static int do_mtd_markbad(struct cmd_tbl *cmdtp, int flag, int argc, ++ char * const argv[]) ++{ ++ struct mtd_info *mtd; ++ loff_t off; ++ int ret; ++ ++ if (argc < 3) ++ return CMD_RET_USAGE; ++ ++ mtd = get_mtd_by_name(argv[1]); ++ if (IS_ERR(mtd) || !mtd) ++ return CMD_RET_FAILURE; ++ ++ if (!mtd_can_have_bb(mtd)) { ++ printf("Only NAND-based devices can have mark blocks\n"); ++ goto out_put_mtd; ++ } ++ ++ off = simple_strtoull(argv[2], NULL, 0); ++ ++ ret = mtd_block_markbad(mtd, off); ++ if (!ret) { ++ printf("MTD device %s block at 0x%08llx marked bad\n", ++ mtd->name, off); ++ } else { ++ printf("MTD device %s block at 0x%08llx mark bad failed\n", ++ mtd->name, off); ++ } ++ ++out_put_mtd: ++ put_mtd_device(mtd); ++ ++ return CMD_RET_SUCCESS; ++} ++ + #ifdef CONFIG_AUTO_COMPLETE + static int mtd_name_complete(int argc, char *const argv[], char last_char, + int maxv, char *cmdv[]) +@@ -775,6 +811,7 @@ U_BOOT_LONGHELP(mtd, + "\n" + "Specific functions:\n" + "mtd bad \n" ++ "mtd markbad \n" + #if CONFIG_IS_ENABLED(CMD_MTD_OTP) + "mtd otpread [u|f] \n" + "mtd otpwrite \n" +@@ -815,4 +852,6 @@ U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils" + U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, + mtd_name_complete), + U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, ++ mtd_name_complete), ++ U_BOOT_SUBCMD_MKENT_COMPLETE(markbad, 3, 1, do_mtd_markbad, + mtd_name_complete)); diff --git a/patch/u-boot/u-boot-filogic/100-11-env-add-support-for-NMBM-upper-MTD-layer.patch b/patch/u-boot/u-boot-filogic/100-11-env-add-support-for-NMBM-upper-MTD-layer.patch new file mode 100644 index 0000000000..a02a59a057 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-11-env-add-support-for-NMBM-upper-MTD-layer.patch @@ -0,0 +1,260 @@ +From 240d98e6ad0aed3c11236aa40a60bbd6fe01fae5 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:50:46 +0800 +Subject: [PATCH 45/71] env: add support for NMBM upper MTD layer + +Add an env driver for NMBM upper MTD layer + +Signed-off-by: Weijie Gao +--- + env/Kconfig | 19 ++++- + env/Makefile | 1 + + env/env.c | 3 + + env/nmbm.c | 155 +++++++++++++++++++++++++++++++++++++++++ + include/env_internal.h | 1 + + tools/Makefile | 1 + + 6 files changed, 178 insertions(+), 2 deletions(-) + create mode 100644 env/nmbm.c + +--- a/env/Kconfig ++++ b/env/Kconfig +@@ -74,7 +74,7 @@ config ENV_IS_DEFAULT + !ENV_IS_IN_MMC && !ENV_IS_IN_NAND && \ + !ENV_IS_IN_NVRAM && !ENV_IS_IN_ONENAND && \ + !ENV_IS_IN_REMOTE && !ENV_IS_IN_SPI_FLASH && \ +- !ENV_IS_IN_UBI && !ENV_IS_IN_MTD ++ !ENV_IS_IN_UBI && !ENV_IS_IN_NMBM && !ENV_IS_IN_MTD + select ENV_IS_NOWHERE + + config ENV_IS_NOWHERE +@@ -318,6 +318,21 @@ config ENV_IS_IN_NAND + Currently, CONFIG_ENV_OFFSET_REDUND is not supported when + using CONFIG_ENV_OFFSET_OOB. + ++config ENV_IS_IN_NMBM ++ bool "Environment in a NMBM upper MTD layer" ++ depends on !CHAIN_OF_TRUST ++ depends on NMBM_MTD ++ help ++ Define this if you have a NMBM upper MTD which you want to use for ++ the environment. ++ ++ - CONFIG_ENV_OFFSET: ++ - CONFIG_ENV_SIZE: ++ ++ These two #defines specify the offset and size of the environment ++ area within the first NAND device. CONFIG_ENV_OFFSET must be ++ aligned to an erase block boundary. ++ + config ENV_RANGE + hex "Length of the region in which the environment can be written" + depends on ENV_IS_IN_NAND +@@ -604,7 +619,7 @@ config ENV_MTD_NAME + config ENV_OFFSET + hex "Environment offset" + depends on ENV_IS_IN_EEPROM || ENV_IS_IN_MMC || ENV_IS_IN_NAND || \ +- ENV_IS_IN_SPI_FLASH || ENV_IS_IN_MTD ++ ENV_IS_IN_SPI_FLASH || ENV_IS_IN_NMBM || ENV_IS_IN_MTD + default 0x3f8000 if ARCH_ROCKCHIP && ENV_IS_IN_MMC + default 0x140000 if ARCH_ROCKCHIP && ENV_IS_IN_SPI_FLASH + default 0xF0000 if ARCH_SUNXI +--- a/env/Makefile ++++ b/env/Makefile +@@ -26,6 +26,7 @@ obj-$(CONFIG_$(PHASE_)ENV_IS_IN_FAT) += + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_EXT4) += ext4.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_MTD) += mtd.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_NAND) += nand.o ++obj-$(CONFIG_$(PHASE_)ENV_IS_IN_NMBM) += nmbm.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_SPI_FLASH) += sf.o + obj-$(CONFIG_$(PHASE_)ENV_IS_IN_FLASH) += flash.o + +--- a/env/env.c ++++ b/env/env.c +@@ -52,6 +52,9 @@ static enum env_location env_locations[] + #ifdef CONFIG_ENV_IS_IN_NAND + ENVL_NAND, + #endif ++#ifdef CONFIG_ENV_IS_IN_NMBM ++ ENVL_NMBM, ++#endif + #ifdef CONFIG_ENV_IS_IN_NVRAM + ENVL_NVRAM, + #endif +--- /dev/null ++++ b/env/nmbm.c +@@ -0,0 +1,155 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (C) 2020 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#if defined(CONFIG_CMD_SAVEENV) && defined(CONFIG_NMBM_MTD) ++#define CMD_SAVEENV ++#endif ++ ++#if defined(ENV_IS_EMBEDDED) ++env_t *env_ptr = &environment; ++#else /* ! ENV_IS_EMBEDDED */ ++env_t *env_ptr; ++#endif /* ENV_IS_EMBEDDED */ ++ ++DECLARE_GLOBAL_DATA_PTR; ++ ++static int env_nmbm_init(void) ++{ ++#if defined(ENV_IS_EMBEDDED) ++ int crc1_ok = 0, crc2_ok = 0; ++ env_t *tmp_env1; ++ ++ tmp_env1 = env_ptr; ++ crc1_ok = crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc; ++ ++ if (!crc1_ok && !crc2_ok) { ++ gd->env_addr = 0; ++ gd->env_valid = ENV_INVALID; ++ ++ return 0; ++ } else if (crc1_ok && !crc2_ok) { ++ gd->env_valid = ENV_VALID; ++ } ++ ++ if (gd->env_valid == ENV_VALID) ++ env_ptr = tmp_env1; ++ ++ gd->env_addr = (ulong)env_ptr->data; ++ ++#else /* ENV_IS_EMBEDDED */ ++ gd->env_addr = (ulong)&default_environment[0]; ++ gd->env_valid = ENV_VALID; ++#endif /* ENV_IS_EMBEDDED */ ++ ++ return 0; ++} ++ ++#ifdef CMD_SAVEENV ++static int env_nmbm_save(void) ++{ ++ ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1); ++ struct mtd_info *mtd; ++ struct erase_info ei; ++ int ret = 0; ++ ++ ret = env_export(env_new); ++ if (ret) ++ return ret; ++ ++ mtd = nmbm_mtd_get_upper_by_index(0); ++ if (!mtd) ++ return 1; ++ ++ printf("Erasing on NMBM...\n"); ++ memset(&ei, 0, sizeof(ei)); ++ ++ ei.mtd = mtd; ++ ei.addr = CONFIG_ENV_OFFSET; ++ ei.len = CONFIG_ENV_SIZE; ++ ++ if (mtd_erase(mtd, &ei)) ++ return 1; ++ ++ printf("Writing on NMBM... "); ++ ret = mtd_write(mtd, CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, NULL, ++ (u_char *)env_new); ++ puts(ret ? "FAILED!\n" : "OK\n"); ++ ++ return !!ret; ++} ++#endif /* CMD_SAVEENV */ ++ ++static int readenv(size_t offset, u_char *buf) ++{ ++ struct mtd_info *mtd; ++ struct mtd_oob_ops ops; ++ int ret; ++ size_t len = CONFIG_ENV_SIZE; ++ ++ mtd = nmbm_mtd_get_upper_by_index(0); ++ if (!mtd) ++ return 1; ++ ++ ops.mode = MTD_OPS_AUTO_OOB; ++ ops.ooblen = 0; ++ while(len > 0) { ++ ops.datbuf = buf; ++ ops.len = min(len, (size_t)mtd->writesize); ++ ops.oobbuf = NULL; ++ ++ ret = mtd_read_oob(mtd, offset, &ops); ++ if (ret) ++ return 1; ++ ++ buf += mtd->writesize; ++ len -= mtd->writesize; ++ offset += mtd->writesize; ++ } ++ ++ return 0; ++} ++ ++static int env_nmbm_load(void) ++{ ++#if !defined(ENV_IS_EMBEDDED) ++ ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE); ++ int ret; ++ ++ ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf); ++ if (ret) { ++ env_set_default("readenv() failed", 0); ++ return -EIO; ++ } ++ ++ return env_import(buf, 1, H_EXTERNAL); ++#endif /* ! ENV_IS_EMBEDDED */ ++ ++ return 0; ++} ++ ++U_BOOT_ENV_LOCATION(nmbm) = { ++ .location = ENVL_NMBM, ++ ENV_NAME("NMBM") ++ .load = env_nmbm_load, ++#if defined(CMD_SAVEENV) ++ .save = env_save_ptr(env_nmbm_save), ++#endif ++ .init = env_nmbm_init, ++}; +--- a/include/env_internal.h ++++ b/include/env_internal.h +@@ -110,6 +110,7 @@ enum env_location { + ENVL_MMC, + ENVL_MTD, + ENVL_NAND, ++ ENVL_NMBM, + ENVL_NVRAM, + ENVL_ONENAND, + ENVL_REMOTE, +--- a/tools/Makefile ++++ b/tools/Makefile +@@ -39,6 +39,7 @@ ENVCRC-$(CONFIG_ENV_IS_IN_FLASH) = y + ENVCRC-$(CONFIG_ENV_IS_IN_ONENAND) = y + ENVCRC-$(CONFIG_ENV_IS_IN_MTD) = y + ENVCRC-$(CONFIG_ENV_IS_IN_NAND) = y ++ENVCRC-$(CONFIG_ENV_IS_IN_NMBM) = y + ENVCRC-$(CONFIG_ENV_IS_IN_NVRAM) = y + ENVCRC-$(CONFIG_ENV_IS_IN_SPI_FLASH) = y + BUILD_ENVCRC ?= $(ENVCRC-y) diff --git a/patch/u-boot/u-boot-filogic/100-12-mtd-mtk-snand-add-NMBM-support-for-SPL.patch b/patch/u-boot/u-boot-filogic/100-12-mtd-mtk-snand-add-NMBM-support-for-SPL.patch new file mode 100644 index 0000000000..32b21be255 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-12-mtd-mtk-snand-add-NMBM-support-for-SPL.patch @@ -0,0 +1,173 @@ +From 9e8ac4fc7125795ac5e8834aaf454fd45b99c580 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:53:03 +0800 +Subject: [PATCH 46/71] mtd: mtk-snand: add NMBM support for SPL + +Add NMBM support for mtk-snand SPL loader + +Signed-off-by: Weijie Gao +--- + drivers/mtd/mtk-snand/mtk-snand-spl.c | 127 ++++++++++++++++++++++++++ + 1 file changed, 127 insertions(+) + +--- a/drivers/mtd/mtk-snand/mtk-snand-spl.c ++++ b/drivers/mtd/mtk-snand/mtk-snand-spl.c +@@ -13,12 +13,134 @@ + #include + #include + ++#include ++ + #include "mtk-snand.h" + + static struct mtk_snand *snf; + static struct mtk_snand_chip_info cinfo; + static u32 oobavail; + ++#ifdef CONFIG_ENABLE_NAND_NMBM ++static struct nmbm_instance *ni; ++ ++static int nmbm_lower_read_page(void *arg, uint64_t addr, void *buf, void *oob, ++ enum nmbm_oob_mode mode) ++{ ++ int ret; ++ bool raw = mode == NMBM_MODE_RAW ? true : false; ++ ++ if (mode == NMBM_MODE_AUTO_OOB) { ++ ret = mtk_snand_read_page_auto_oob(snf, addr, buf, oob, ++ oobavail, NULL, false); ++ } else { ++ ret = mtk_snand_read_page(snf, addr, buf, oob, raw); ++ } ++ ++ if (ret == -EBADMSG) ++ return 1; ++ else if (ret >= 0) ++ return 0; ++ ++ return ret; ++} ++ ++static int nmbm_lower_write_page(void *arg, uint64_t addr, const void *buf, ++ const void *oob, enum nmbm_oob_mode mode) ++{ ++ bool raw = mode == NMBM_MODE_RAW ? true : false; ++ ++ if (mode == NMBM_MODE_AUTO_OOB) { ++ return mtk_snand_write_page_auto_oob(snf, addr, buf, oob, ++ oobavail, NULL, false); ++ } ++ ++ return mtk_snand_write_page(snf, addr, buf, oob, raw); ++} ++ ++static int nmbm_lower_erase_block(void *arg, uint64_t addr) ++{ ++ return mtk_snand_erase_block(snf, addr); ++} ++ ++static int nmbm_lower_is_bad_block(void *arg, uint64_t addr) ++{ ++ return mtk_snand_block_isbad(snf, addr); ++} ++ ++static int nmbm_lower_mark_bad_block(void *arg, uint64_t addr) ++{ ++ return mtk_snand_block_markbad(snf, addr); ++} ++ ++static void nmbm_lower_log(void *arg, enum nmbm_log_category level, ++ const char *fmt, va_list ap) ++{ ++ vprintf(fmt, ap); ++} ++ ++static int nmbm_init(void) ++{ ++ struct nmbm_lower_device nld; ++ size_t ni_size; ++ int ret; ++ ++ memset(&nld, 0, sizeof(nld)); ++ ++ nld.flags = NMBM_F_CREATE; ++ nld.max_ratio = CONFIG_NMBM_MAX_RATIO; ++ nld.max_reserved_blocks = CONFIG_NMBM_MAX_BLOCKS; ++ ++ nld.size = cinfo.chipsize; ++ nld.erasesize = cinfo.blocksize; ++ nld.writesize = cinfo.pagesize; ++ nld.oobsize = cinfo.sparesize; ++ nld.oobavail = oobavail; ++ ++ nld.read_page = nmbm_lower_read_page; ++ nld.write_page = nmbm_lower_write_page; ++ nld.erase_block = nmbm_lower_erase_block; ++ nld.is_bad_block = nmbm_lower_is_bad_block; ++ nld.mark_bad_block = nmbm_lower_mark_bad_block; ++ ++ nld.logprint = nmbm_lower_log; ++ ++ ni_size = nmbm_calc_structure_size(&nld); ++ ni = malloc(ni_size); ++ if (!ni) { ++ printf("Failed to allocate memory (0x%u) for NMBM instance\n", ++ ni_size); ++ return -ENOMEM; ++ } ++ ++ memset(ni, 0, ni_size); ++ ++ printf("Initializing NMBM ...\n"); ++ ++ ret = nmbm_attach(&nld, ni); ++ if (ret) { ++ ni = NULL; ++ return ret; ++ } ++ ++ return 0; ++} ++ ++int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst) ++{ ++ size_t retlen; ++ ++ if (!ni) ++ return -ENODEV; ++ ++ nmbm_read_range(ni, offs, size, dst, NMBM_MODE_PLACE_OOB, &retlen); ++ if (retlen != size) ++ return -EIO; ++ ++ return 0; ++} ++ ++#else + static u8 *page_cache; + + int nand_spl_load_image(uint32_t offs, unsigned int size, void *dst) +@@ -60,6 +182,7 @@ int nand_spl_load_image(uint32_t offs, u + + return ret; + } ++#endif + + void nand_init(void) + { +@@ -105,11 +228,15 @@ void nand_init(void) + printf("SPI-NAND: %s (%uMB)\n", cinfo.model, + (u32)(cinfo.chipsize >> 20)); + ++#ifdef CONFIG_ENABLE_NAND_NMBM ++ nmbm_init(); ++#else + page_cache = malloc(cinfo.pagesize + cinfo.sparesize); + if (!page_cache) { + mtk_snand_cleanup(snf); + printf("mtk-snand-spl: failed to allocate page cache\n"); + } ++#endif + } + + void nand_deselect(void) diff --git a/patch/u-boot/u-boot-filogic/100-13-cmd-add-a-new-command-for-NAND-flash-debugging.patch b/patch/u-boot/u-boot-filogic/100-13-cmd-add-a-new-command-for-NAND-flash-debugging.patch new file mode 100644 index 0000000000..83a5a3f234 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-13-cmd-add-a-new-command-for-NAND-flash-debugging.patch @@ -0,0 +1,1118 @@ +From 88271cb3ae9c68dc200d627653df96fc557c2a64 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 10:55:35 +0800 +Subject: [PATCH 47/71] cmd: add a new command for NAND flash debugging + +Add a command 'nand-ext' for NAND flash debugging: +- Dump a page with oob, with optional raw read support +- Display all bad blocks +- Mark a block as bad block +- Set a bitflip on a page +- Erase +- Read / write data from/to any offset with any size +- Read / write pages with oob +- Erase, read and write support skip bad block or forced mode, support + raw mode, supporot auto-oob mode +- Supports operating on a specific partition +- No need to specify NAND device name + +Signed-off-by: Weijie Gao +--- + cmd/Kconfig | 8 + + cmd/Makefile | 1 + + cmd/nand-ext.c | 1062 ++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 1071 insertions(+) + create mode 100644 cmd/nand-ext.c + +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -1525,6 +1525,14 @@ config CMD_NAND_WATCH + + endif # CMD_NAND + ++config CMD_NAND_EXT ++ bool "nand - extended nand utility for debugging" ++ depends on !CMD_NAND ++ default y if MTD_RAW_NAND || MTD_SPI_NAND || MTK_SPI_NAND ++ select MTD_PARTITIONS ++ help ++ NAND flash R/W and debugging support. ++ + config CMD_NMBM + depends on NMBM_MTD + bool "nmbm" +--- a/cmd/Makefile ++++ b/cmd/Makefile +@@ -130,6 +130,7 @@ obj-y += legacy-mtd-utils.o + endif + obj-$(CONFIG_CMD_MUX) += mux.o + obj-$(CONFIG_CMD_NAND) += nand.o ++obj-$(CONFIG_CMD_NAND_EXT) += nand-ext.o + obj-$(CONFIG_CMD_NMBM) += nmbm.o + ifdef CONFIG_NET + obj-$(CONFIG_CMD_NET) += net.o net-common.o +--- /dev/null ++++ b/cmd/nand-ext.c +@@ -0,0 +1,1062 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (C) 2021 MediaTek Inc. All Rights Reserved. ++ * ++ * Author: Weijie Gao ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static struct mtd_info *curr_dev; ++ ++static void mtd_show_parts(struct mtd_info *mtd, int level) ++{ ++ struct mtd_info *part; ++ int i; ++ ++ list_for_each_entry(part, &mtd->partitions, node) { ++ for (i = 0; i < level; i++) ++ printf("\t"); ++ printf(" - 0x%012llx-0x%012llx : \"%s\"\n", ++ part->offset, part->offset + part->size, part->name); ++ ++ mtd_show_parts(part, level + 1); ++ } ++} ++ ++static void mtd_show_device(struct mtd_info *mtd) ++{ ++ /* Device */ ++ printf("* %s\n", mtd->name); ++#if defined(CONFIG_DM) ++ if (mtd->dev) { ++ printf(" - device: %s\n", mtd->dev->name); ++ printf(" - parent: %s\n", mtd->dev->parent->name); ++ printf(" - driver: %s\n", mtd->dev->driver->name); ++ } ++#endif ++ ++ /* MTD device information */ ++ printf(" - type: "); ++ switch (mtd->type) { ++ case MTD_NANDFLASH: ++ printf("NAND flash\n"); ++ break; ++ case MTD_MLCNANDFLASH: ++ printf("MLC NAND flash\n"); ++ break; ++ case MTD_ABSENT: ++ default: ++ printf("Not supported\n"); ++ break; ++ } ++ ++ printf(" - block size: 0x%x bytes\n", mtd->erasesize); ++ printf(" - page size: 0x%x bytes\n", mtd->writesize); ++ printf(" - OOB size: %u bytes\n", mtd->oobsize); ++ printf(" - OOB available: %u bytes\n", mtd->oobavail); ++ ++ if (mtd->ecc_strength) { ++ printf(" - ECC strength: %u bits\n", mtd->ecc_strength); ++ printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); ++ printf(" - bitflip threshold: %u bits\n", ++ mtd->bitflip_threshold); ++ } ++ ++ printf(" - 0x%012llx-0x%012llx : \"%s\"\n", ++ mtd->offset, mtd->offset + mtd->size, mtd->name); ++ ++ /* MTD partitions, if any */ ++ mtd_show_parts(mtd, 1); ++} ++ ++static int do_nand_list(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd; ++ int dev_nb = 0; ++ ++ /* Ensure all devices (and their partitions) are probed */ ++ mtd_probe_devices(); ++ ++ printf("List of NAND devices:\n"); ++ mtd_for_each_device(mtd) { ++ if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) ++ continue; ++ ++ if (!mtd_is_partition(mtd)) ++ mtd_show_device(mtd); ++ ++ dev_nb++; ++ } ++ ++ if (!dev_nb) ++ printf("No NAND MTD device found\n"); ++ ++ return CMD_RET_SUCCESS; ++} ++ ++static struct mtd_info *nand_get_curr_dev(void) ++{ ++ struct mtd_info *mtd, *first_dev = NULL; ++ int err, dev_nb = 0; ++ ++ if (curr_dev) { ++ mtd = get_mtd_device(curr_dev, -1); ++ if (!IS_ERR_OR_NULL(mtd)) { ++ __put_mtd_device(mtd); ++ return mtd; ++ } ++ ++ curr_dev = NULL; ++ } ++ ++ /* Ensure all devices (and their partitions) are probed */ ++ mtd_probe_devices(); ++ ++ mtd_for_each_device(mtd) { ++ if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) ++ continue; ++ ++ if (!mtd_is_partition(mtd)) { ++ if (!first_dev) ++ first_dev = mtd; ++ dev_nb++; ++ } ++ } ++ ++ if (!dev_nb) { ++ printf("No NAND MTD device found\n"); ++ return NULL; ++ } ++ ++ if (dev_nb > 1) { ++ printf("No active NAND MTD device specified\n"); ++ return NULL; ++ } ++ ++ err = __get_mtd_device(first_dev); ++ if (err) { ++ printf("Failed to get MTD device '%s': err %d\n", ++ first_dev->name, err); ++ return NULL; ++ } ++ ++ curr_dev = first_dev; ++ ++ printf("'%s' is now active device\n", first_dev->name); ++ ++ return curr_dev; ++} ++ ++static struct mtd_info *nand_get_part(struct mtd_info *master, ++ const char *name) ++{ ++ struct mtd_info *slave; ++ ++ list_for_each_entry(slave, &master->partitions, node) { ++ if (!strcmp(slave->name, name)) ++ return slave; ++ } ++ ++ return NULL; ++} ++ ++static int do_nand_info(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(); ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ mtd_show_device(mtd); ++ ++ return 0; ++} ++ ++static int do_nand_select(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd, *old; ++ ++ if (argc < 2) { ++ printf("MTD device name must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ mtd = get_mtd_device_nm(argv[1]); ++ if (!mtd) { ++ printf("MTD device '%s' not found\n", argv[1]); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (mtd_is_partition(mtd)) { ++ printf("Error: '%s' is a MTD partition\n", argv[1]); ++ __put_mtd_device(mtd); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (mtd->type != MTD_NANDFLASH && mtd->type != MTD_MLCNANDFLASH) { ++ printf("Error: '%s' is not a NAND device\n", argv[1]); ++ __put_mtd_device(mtd); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (mtd == curr_dev) { ++ __put_mtd_device(mtd); ++ return CMD_RET_SUCCESS; ++ } ++ ++ if (curr_dev) { ++ old = get_mtd_device(curr_dev, -1); ++ if (!IS_ERR_OR_NULL(old)) { ++ __put_mtd_device(old); ++ __put_mtd_device(curr_dev); ++ } ++ ++ curr_dev = NULL; ++ } ++ ++ curr_dev = mtd; ++ ++ printf("'%s' is now active device\n", curr_dev->name); ++ ++ return CMD_RET_SUCCESS; ++} ++ ++static void dump_buf(const u8 *data, size_t size, u64 addr) ++{ ++ const u8 *p = data; ++ u32 i, chklen; ++ ++ while (size) { ++ chklen = 16; ++ if (chklen > size) ++ chklen = (u32)size; ++ ++ printf("%08llx: ", addr); ++ ++ for (i = 0; i < chklen; i++) { ++ if (i && (i % 4 == 0)) ++ printf(" "); ++ ++ printf("%02x ", p[i]); ++ } ++ ++ for (i = chklen; i < 16; i++) { ++ if (i && (i % 4 == 0)) ++ printf(" "); ++ ++ printf(" "); ++ } ++ printf(" "); ++ ++ for (i = 0; i < chklen; i++) { ++ if (p[i] < 32 || p[i] >= 0x7f) ++ printf("."); ++ else ++ printf("%c", p[i]); ++ } ++ printf("\n"); ++ ++ p += chklen; ++ size -= chklen; ++ addr += chklen; ++ } ++} ++ ++static int do_nand_dump(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(); ++ struct mtd_oob_ops io_op = {}; ++ bool raw = false; ++ int ret; ++ u64 off; ++ u8 *buf; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ if (strstr(argv[0], ".raw")) ++ raw = true; ++ ++ if (argc < 2) { ++ printf("Dump offset must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ off = simple_strtoull(argv[1], NULL, 0); ++ if (off >= mtd->size) { ++ printf("Offset 0x%llx is larger than flash size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ off &= ~(u64)mtd->writesize_mask; ++ ++ buf = malloc(mtd->writesize + mtd->oobsize); ++ if (!buf) { ++ printf("Failed to allocate buffer\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; ++ io_op.len = mtd->writesize; ++ io_op.datbuf = buf; ++ io_op.ooblen = mtd->oobsize; ++ io_op.oobbuf = buf + mtd->writesize; ++ ++ ret = mtd_read_oob(mtd, off, &io_op); ++ if (ret < 0 && ret != -EUCLEAN && ret != -EBADMSG) { ++ printf("Failed to read page at 0x%llx, err %d\n", off, ret); ++ free(buf); ++ return CMD_RET_FAILURE; ++ } ++ ++ printf("Dump of %spage at 0x%llx:\n", raw ? "raw " : "", off); ++ dump_buf(buf, mtd->writesize, off); ++ ++ printf("\n"); ++ printf("OOB:\n"); ++ dump_buf(buf + mtd->writesize, mtd->oobsize, 0); ++ ++ free(buf); ++ ++ return CMD_RET_SUCCESS; ++} ++ ++static int do_nand_bad(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(); ++ u64 off = 0; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ while (off < mtd->size) { ++ if (mtd_block_isbad(mtd, off)) ++ printf("\t%08llx\n", off); ++ ++ off += mtd->erasesize; ++ } ++ ++ return 0; ++} ++ ++static int do_nand_markbad(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(); ++ u64 off; ++ int ret; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ if (argc < 2) { ++ printf("Missing address within a block to be marked bad\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ off = simple_strtoull(argv[1], NULL, 0); ++ if (off >= mtd->size) { ++ printf("Offset 0x%llx is larger than flash size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ off &= ~(u64)mtd->erasesize_mask; ++ ++ ret = mtd_block_markbad(mtd, off); ++ ++ if (!ret) ++ printf("Block at 0x%08llx has been marked bad\n", off); ++ else ++ printf("Failed to mark bad block at 0x%08llx\n", off); ++ ++ return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; ++} ++ ++static int do_nand_bitflip(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(); ++ struct mtd_oob_ops io_op = {}; ++ u32 col, bit; ++ bool res; ++ u64 off; ++ u8 *buf; ++ int ret; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ if (argc < 2) { ++ printf("Missing address to generate bitflip\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ off = simple_strtoull(argv[1], NULL, 0); ++ if (off >= mtd->size) { ++ printf("Offset 0x%llx is larger than flash size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 3) { ++ printf("Missing column address\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ col = simple_strtoul(argv[2], NULL, 0); ++ if (col >= mtd->writesize + mtd->oobsize) { ++ printf("Column address must be less than %u\n", ++ mtd->writesize + mtd->oobsize); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 4) { ++ printf("Missing bit position\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ bit = simple_strtoul(argv[3], NULL, 0); ++ if (bit > 7) { ++ printf("Bit position must be less than 8\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ off &= ~(u64)mtd->writesize_mask; ++ ++ buf = malloc(mtd->writesize + mtd->oobsize); ++ if (!buf) { ++ printf("Failed to allocate buffer\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ io_op.mode = MTD_OPS_RAW; ++ io_op.len = mtd->writesize; ++ io_op.datbuf = buf; ++ io_op.ooblen = mtd->oobsize; ++ io_op.oobbuf = buf + mtd->writesize; ++ ++ ret = mtd_read_oob(mtd, off, &io_op); ++ if (ret < 0) { ++ printf("Failed to read page at 0x%llx, err %d\n", off, ret); ++ free(buf); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (!(buf[col] & (1 << bit))) { ++ printf("Bit %u at byte %u is already zero\n", bit, col); ++ free(buf); ++ return CMD_RET_FAILURE; ++ } ++ ++ buf[col] &= ~(1 << bit); ++ ++ memset(&io_op, 0, sizeof(io_op)); ++ io_op.mode = MTD_OPS_RAW; ++ io_op.len = mtd->writesize; ++ io_op.datbuf = buf; ++ io_op.ooblen = mtd->oobsize; ++ io_op.oobbuf = buf + mtd->writesize; ++ ++ ret = mtd_write_oob(mtd, off, &io_op); ++ ++ if (ret < 0) { ++ printf("Failed to write page at 0x%llx, err %d\n", off, ret); ++ return CMD_RET_FAILURE; ++ } ++ ++ memset(&io_op, 0, sizeof(io_op)); ++ io_op.mode = MTD_OPS_RAW; ++ io_op.len = mtd->writesize; ++ io_op.datbuf = buf; ++ io_op.ooblen = mtd->oobsize; ++ io_op.oobbuf = buf + mtd->writesize; ++ ++ ret = mtd_read_oob(mtd, off, &io_op); ++ if (ret < 0) { ++ printf("Failed to read page at 0x%llx, err %d\n", off, ret); ++ free(buf); ++ return CMD_RET_FAILURE; ++ } ++ ++ res = (buf[col] & (1 << bit)) == 0; ++ free(buf); ++ ++ if (res) { ++ printf("Bit %u at byte %u has been changed to 0\n", bit, col); ++ return CMD_RET_SUCCESS; ++ } ++ ++ printf("Failed to change bit %u at byte %u to 0\n", bit, col); ++ return CMD_RET_FAILURE; ++} ++ ++static int do_nand_erase(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(), *part; ++ bool spread = false, force = false; ++ u64 off, size, end, limit; ++ struct erase_info ei; ++ char *ends; ++ int ret; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ if (strstr(argv[0], ".spread")) ++ spread = true; ++ ++ if (strstr(argv[0], ".force")) ++ force = true; ++ ++ if (spread && force) { ++ printf("spread and force must not be set at the same time\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 2) { ++ printf("Erase start offset/partition must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ part = nand_get_part(mtd, argv[1]); ++ if (part) { ++ off = part->offset; ++ ++ if (argc < 3) ++ size = part->size; ++ else ++ size = simple_strtoull(argv[2], NULL, 0); ++ ++ if (size > part->size) { ++ printf("Erase end offset is larger than partition size\n"); ++ printf("Erase size reduced to 0x%llx\n", part->size); ++ ++ size = part->size; ++ } ++ ++ limit = off + part->size; ++ } else { ++ off = simple_strtoull(argv[1], &ends, 0); ++ ++ if (ends == argv[1] || *ends) { ++ printf("Partition '%s' not found\n", argv[1]); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (off >= mtd->size) { ++ printf("Offset 0x%llx is larger than flash size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 3) { ++ printf("Erase size offset must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ size = simple_strtoull(argv[2], NULL, 0); ++ ++ if (off + size > mtd->size) { ++ printf("Erase end offset is larger than flash size\n"); ++ ++ size = mtd->size - off; ++ printf("Erase size reduced to 0x%llx\n", size); ++ } ++ ++ limit = mtd->size; ++ } ++ ++ end = off + size; ++ off &= ~(u64)mtd->erasesize_mask; ++ end = (end + mtd->erasesize_mask) & (~(u64)mtd->erasesize_mask); ++ size = end - off; ++ ++ printf("Erasing from 0x%llx to 0x%llx, size 0x%llx ...\n", ++ off, end - 1, end - off); ++ ++ while (size && off < limit) { ++ if (mtd_block_isbad(mtd, off)) { ++ printf("Bad block at 0x%llx", off); ++ ++ if (spread) { ++ printf(" ... skipped\n"); ++ off += mtd->erasesize; ++ continue; ++ } ++ ++ if (!force) { ++ printf(" ... aborted\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ printf(" ... will be force erased\n"); ++ } ++ ++ memset(&ei, 0, sizeof(ei)); ++ ++ ei.mtd = mtd; ++ ei.addr = off; ++ ei.len = mtd->erasesize; ++ ei.scrub = force; ++ ++ ret = mtd_erase(mtd, &ei); ++ if (ret) { ++ printf("Erase failed at 0x%llx\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ off += mtd->erasesize; ++ size -= mtd->erasesize; ++ } ++ ++ printf("Succeeded\n"); ++ ++ return CMD_RET_SUCCESS; ++} ++ ++static bool is_empty_page(const u8 *buf, size_t size) ++{ ++ size_t i; ++ ++ for (i = 0; i < size; i++) { ++ if (buf[i] != 0xff) ++ return false; ++ } ++ ++ return true; ++} ++ ++static int do_nand_io_normal(int argc, char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(), *part; ++ bool spread = false, force = false, raw = false, writeff = false; ++ bool read = false, checkbad = true; ++ struct mtd_oob_ops io_op = {}; ++ size_t size, padding, chksz; ++ uintptr_t addr; ++ u64 off, offp; ++ char *ends; ++ u8 *buf; ++ int ret; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ if (!strncmp(argv[0], "read", 4)) ++ read = true; ++ ++ if (strstr(argv[0], ".spread")) ++ spread = true; ++ ++ if (strstr(argv[0], ".force")) ++ force = true; ++ ++ if (strstr(argv[0], ".raw")) ++ raw = true; ++ ++ if (strstr(argv[0], ".ff")) ++ writeff = true; ++ ++ if (spread && force) { ++ printf("spread and force must not be set at the same time\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 2) { ++ printf("Data address must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ addr = simple_strtoul(argv[1], NULL, 0); ++ ++ if (argc < 3) { ++ printf("Flash address/partition must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ part = nand_get_part(mtd, argv[2]); ++ if (part) { ++ if (argc < 4) { ++ off = 0; ++ } else { ++ off = simple_strtoull(argv[3], NULL, 0); ++ if (off + part->offset >= part->size) { ++ printf("Offset is larger than partition size\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ if (argc < 5) { ++ size = part->size - off; ++ } else { ++ size = simple_strtoul(argv[4], NULL, 0); ++ if (off + size > part->size) { ++ printf("Data size is too large\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ off += part->offset; ++ } else { ++ off = simple_strtoull(argv[2], &ends, 0); ++ ++ if (ends == argv[1] || *ends) { ++ printf("Partition '%s' not found\n", argv[2]); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (off >= mtd->size) { ++ printf("Offset 0x%llx is larger than flash size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 4) { ++ printf("Data size must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ size = simple_strtoul(argv[3], NULL, 0); ++ if (off + size > mtd->size) { ++ printf("Data size is too large\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ buf = malloc(mtd->writesize); ++ if (!buf) { ++ printf("Failed to allocate buffer\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ printf("%s from 0x%llx to 0x%llx, size 0x%zx ...\n", ++ read ? "Reading" : "Writing", off, off + size - 1, size); ++ ++ while (size && off < mtd->size) { ++ if (checkbad || !(off & mtd->erasesize_mask)) { ++ offp = off & ~(u64)mtd->erasesize_mask; ++ ++ if (mtd_block_isbad(mtd, offp)) { ++ printf("Bad block at 0x%llx", offp); ++ ++ if (spread) { ++ printf(" ... skipped\n"); ++ off += mtd->erasesize; ++ checkbad = true; ++ continue; ++ } ++ ++ if (!force) { ++ printf(" ... aborted\n"); ++ goto err_out; ++ } ++ ++ printf(" ... continue\n"); ++ } ++ ++ checkbad = false; ++ } ++ ++ padding = off & mtd->writesize_mask; ++ chksz = mtd->writesize - padding; ++ chksz = min_t(size_t, chksz, size); ++ ++ offp = off & ~(u64)mtd->writesize_mask; ++ ++ memset(&io_op, 0, sizeof(io_op)); ++ io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_PLACE_OOB; ++ io_op.len = mtd->writesize; ++ ++ if (chksz < mtd->writesize) ++ io_op.datbuf = buf; ++ else ++ io_op.datbuf = (void *)addr; ++ ++ if (read) { ++ ret = mtd_read_oob(mtd, offp, &io_op); ++ if (ret && ret != -EUCLEAN && ret != -EBADMSG) ++ goto io_err; ++ ++ if (chksz < mtd->writesize) ++ memcpy((void *)addr, buf + padding, chksz); ++ } else { ++ if (chksz < mtd->writesize) { ++ memset(buf, 0xff, mtd->writesize); ++ memcpy(buf + padding, (void *)addr, chksz); ++ } ++ ++ if (is_empty_page(io_op.datbuf, io_op.len) && !writeff) ++ ret = 0; ++ else ++ ret = mtd_write_oob(mtd, offp, &io_op); ++ ++ if (ret) ++ goto io_err; ++ } ++ ++ size -= chksz; ++ addr += chksz; ++ off += chksz; ++ } ++ ++ if (!size) { ++ printf("Succeeded\n"); ++ ret = CMD_RET_SUCCESS; ++ goto out; ++ } ++ ++ printf("0x%zx byte%s remained for %s\n", size, size > 1 ? "s" : "", ++ read ? "read" : "write"); ++ goto err_out; ++ ++io_err: ++ printf("%s error %d at 0x%llx\n", read ? "Read" : "Write", ret, offp); ++ ++err_out: ++ ret = CMD_RET_FAILURE; ++ ++out: ++ free(buf); ++ return ret; ++} ++ ++static int do_nand_io_page(int argc, char *const argv[]) ++{ ++ struct mtd_info *mtd = nand_get_curr_dev(), *part; ++ bool spread = false, force = false, raw = false, autooob = false; ++ bool read = false, checkbad = true, writeff = false; ++ struct mtd_oob_ops io_op = {}; ++ uintptr_t addr; ++ u64 off, offp; ++ char *ends; ++ u32 count; ++ int ret; ++ ++ if (!mtd) ++ return CMD_RET_FAILURE; ++ ++ if (!strncmp(argv[0], "read", 4)) ++ read = true; ++ ++ if (strstr(argv[0], ".spread")) ++ spread = true; ++ ++ if (strstr(argv[0], ".force")) ++ force = true; ++ ++ if (strstr(argv[0], ".raw")) ++ raw = true; ++ ++ if (strstr(argv[0], ".auto")) ++ autooob = true; ++ ++ if (spread && force) { ++ printf("spread and force must not be set at the same time\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (raw && autooob) { ++ printf("raw and auto must not be set at the same time\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (argc < 2) { ++ printf("Data address must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ addr = simple_strtoul(argv[1], NULL, 0); ++ ++ if (argc < 3) { ++ printf("Flash address/partition must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ part = nand_get_part(mtd, argv[2]); ++ if (part) { ++ if (argc < 4) { ++ printf("Partition offset / page count must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ if (argc < 5) { ++ off = 0; ++ ++ count = simple_strtoul(argv[3], NULL, 0); ++ if (part->offset + count * mtd->writesize > part->size) { ++ printf("Page count exceeds partition size\n"); ++ return CMD_RET_FAILURE; ++ } ++ } else { ++ off = simple_strtoull(argv[3], NULL, 0); ++ if (off >= part->size) { ++ printf("Offset 0x%llx is larger than partition size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ off &= ~(u64)mtd->writesize_mask; ++ ++ count = simple_strtoul(argv[4], NULL, 0); ++ if (part->offset + off + count * mtd->writesize > part->size) { ++ printf("Page count exceeds partition size\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ off += part->offset; ++ } else { ++ off = simple_strtoull(argv[2], &ends, 0); ++ ++ if (ends == argv[1] || *ends) { ++ printf("Partition '%s' not found\n", argv[2]); ++ return CMD_RET_FAILURE; ++ } ++ ++ if (off >= mtd->size) { ++ printf("Offset 0x%llx is larger than flash size\n", off); ++ return CMD_RET_FAILURE; ++ } ++ ++ off &= ~(u64)mtd->writesize_mask; ++ ++ if (argc < 4) { ++ printf("Page count must be specified\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ count = simple_strtoul(argv[3], NULL, 0); ++ if (off + count * mtd->writesize > mtd->size) { ++ printf("Page count exceeds flash size\n"); ++ return CMD_RET_FAILURE; ++ } ++ } ++ ++ printf("%s from 0x%llx to 0x%llx (+%u), count %u ...\n", ++ read ? "Reading" : "Writing", off, ++ off + count * mtd->writesize - 1, mtd->oobsize, count); ++ ++ while (count && off < mtd->size) { ++ if (checkbad || !(off & mtd->erasesize_mask)) { ++ offp = off & ~(u64)mtd->erasesize_mask; ++ ++ if (mtd_block_isbad(mtd, offp)) { ++ printf("Bad block at 0x%llx", offp); ++ ++ if (spread) { ++ printf(" ... skipped\n"); ++ off += mtd->erasesize; ++ checkbad = true; ++ continue; ++ } ++ ++ if (!force) { ++ printf(" ... aborted\n"); ++ return CMD_RET_FAILURE; ++ } ++ ++ printf(" ... continue\n"); ++ } ++ ++ checkbad = false; ++ } ++ ++ memset(&io_op, 0, sizeof(io_op)); ++ ++ if (raw) ++ io_op.mode = MTD_OPS_RAW; ++ else if (autooob) ++ io_op.mode = MTD_OPS_AUTO_OOB; ++ else ++ io_op.mode = MTD_OPS_PLACE_OOB; ++ ++ io_op.len = mtd->writesize; ++ io_op.ooblen = mtd->oobsize; ++ io_op.datbuf = (void *)addr; ++ io_op.oobbuf = io_op.datbuf + mtd->writesize; ++ ++ if (read) { ++ ret = mtd_read_oob(mtd, off, &io_op); ++ if (ret && ret != -EUCLEAN && ret != -EBADMSG) ++ goto io_err; ++ } else { ++ if (is_empty_page((void *)addr, mtd->writesize + mtd->oobsize) && !writeff) ++ ret = 0; ++ else ++ ret = mtd_write_oob(mtd, off, &io_op); ++ ++ if (ret) ++ goto io_err; ++ } ++ ++ count--; ++ addr += mtd->writesize + mtd->oobsize; ++ off += mtd->writesize; ++ } ++ ++ if (!count) { ++ printf("Succeeded\n"); ++ return CMD_RET_SUCCESS; ++ } ++ ++ printf("%u page%s remained for %s\n", count, count > 1 ? "s" : "", ++ read ? "read" : "write"); ++ return CMD_RET_FAILURE; ++ ++io_err: ++ printf("%s error %d at 0x%llx\n", read ? "Read" : "Write", ret, off); ++ return CMD_RET_FAILURE; ++} ++ ++static int do_nand_io(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ if (strstr(argv[0], ".oob")) ++ return do_nand_io_page(argc, argv); ++ ++ return do_nand_io_normal(argc, argv); ++} ++ ++#ifdef CONFIG_SYS_LONGHELP ++static char nand_help_text[] = ++ "- NAND flash R/W and debugging utility\n" ++ "nand list\n" ++ "nand info - Show active NAND devices\n" ++ "nand select - Select active NAND devices\n" ++ "nand dump[.raw] \n" ++ "nand bad\n" ++ "nand markbad \n" ++ "nand bitflip \n" ++ "nand erase[.spread|.force] [ | []]\n" ++ "nand read[.spread|.force][.raw] \n" ++ " [ []]\n" ++ "nand write[.spread|.force][.raw][.ff] \n" ++ " [ []]\n" ++ "nand read.oob[.spread|.force][.raw|.auto] \n" ++ " [] \n" ++ "nand write.oob[.spread|.force][.raw|.auto][.ff] \n" ++ " [] \n"; ++#endif ++ ++U_BOOT_CMD_WITH_SUBCMDS(nand, "NAND utility", ++ nand_help_text, ++ U_BOOT_SUBCMD_MKENT(list, 1, 0, do_nand_list), ++ U_BOOT_SUBCMD_MKENT(info, 1, 0, do_nand_info), ++ U_BOOT_SUBCMD_MKENT(select, 2, 0, do_nand_select), ++ U_BOOT_SUBCMD_MKENT(dump, 2, 0, do_nand_dump), ++ U_BOOT_SUBCMD_MKENT(bad, 1, 0, do_nand_bad), ++ U_BOOT_SUBCMD_MKENT(markbad, 2, 0, do_nand_markbad), ++ U_BOOT_SUBCMD_MKENT(bitflip, 4, 0, do_nand_bitflip), ++ U_BOOT_SUBCMD_MKENT(erase, 3, 0, do_nand_erase), ++ U_BOOT_SUBCMD_MKENT(read, 5, 0, do_nand_io), ++ U_BOOT_SUBCMD_MKENT(write, 5, 0, do_nand_io) ++); diff --git a/patch/u-boot/u-boot-filogic/100-14-mtd-spi-nor-add-support-to-read-flash-unique-ID.patch b/patch/u-boot/u-boot-filogic/100-14-mtd-spi-nor-add-support-to-read-flash-unique-ID.patch new file mode 100644 index 0000000000..230bbf0fa3 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-14-mtd-spi-nor-add-support-to-read-flash-unique-ID.patch @@ -0,0 +1,142 @@ +From c4172a95df8a57a66c70a8b9948b9600a01c4cb7 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 11:32:08 +0800 +Subject: [PATCH 49/71] mtd: spi-nor: add support to read flash unique ID + +This patch adds support to read unique ID from spi-nor flashes. + +Signed-off-by: Weijie Gao +--- + drivers/mtd/spi/spi-nor-core.c | 95 ++++++++++++++++++++++++++++++++++ + include/linux/mtd/spi-nor.h | 2 + + 2 files changed, 97 insertions(+) + +--- a/drivers/mtd/spi/spi-nor-core.c ++++ b/drivers/mtd/spi/spi-nor-core.c +@@ -3248,6 +3248,100 @@ static int spi_nor_init_params(struct sp + return 0; + } + ++static int spi_nor_read_uuid(struct spi_nor *nor) ++{ ++ u8 read_opcode, addr_width, read_dummy; ++ loff_t addr; ++ u8 *uuid; ++ u8 uuid_len; ++ int shift = 0; ++ int ret; ++ int i; ++ struct spi_mem_op op; ++ ++ read_opcode = nor->read_opcode; ++ addr_width = nor->addr_width; ++ read_dummy = nor->read_dummy; ++ ++ switch (JEDEC_MFR(nor->info)) { ++ case SNOR_MFR_WINBOND: ++ uuid_len = 8; ++ nor->read_opcode = 0x4b; ++ nor->addr_width = 0; ++ addr = 0x0; ++ nor->read_dummy = 4; ++ break; ++ case SNOR_MFR_GIGADEVICE: ++ uuid_len = 16; ++ nor->read_opcode = 0x4b; ++ nor->addr_width = 3; ++ addr = 0x0; ++ nor->read_dummy = 1; ++ break; ++ case CFI_MFR_ST: ++ case SNOR_MFR_MICRON: ++ uuid_len = 17; ++ shift = 3; ++ nor->read_opcode = 0x9f; ++ nor->addr_width = 0; ++ addr = 0x0; ++ nor->read_dummy = 0; ++ break; ++ case SNOR_MFR_EON: ++ uuid_len = 12; ++ nor->read_opcode = 0x5a; ++ nor->addr_width = 3; ++ addr = 0x80; ++ nor->read_dummy = 1; ++ break; ++ /* Automotive only in SPANSION's NOR devices */ ++ case SNOR_MFR_SPANSION: ++ uuid_len = 11; ++ shift = 386; ++ nor->read_opcode = 0x9f; ++ nor->addr_width = 0; ++ addr = 0x0; ++ nor->read_dummy = 0; ++ break; ++ default: ++ printf("UUID not supported on this device.\n"); ++ return -ENOTSUPP; ++ } ++ ++ uuid = kmalloc((uuid_len + shift) * sizeof(*uuid), GFP_KERNEL); ++ if (!uuid) { ++ ret = -ENOMEM; ++ goto read_err; ++ } ++ memset(uuid, 0x0, (uuid_len + shift) * sizeof(*uuid)); ++ ++ op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0), ++ SPI_MEM_OP_ADDR(nor->addr_width, addr, 0), ++ SPI_MEM_OP_DUMMY(nor->read_dummy, 0), ++ SPI_MEM_OP_DATA_IN(uuid_len+shift, NULL, 0)); ++ ++ spi_nor_setup_op(nor, &op, nor->reg_proto); ++ ++ ret = spi_nor_read_write_reg(nor, &op, uuid); ++ if (ret < 0) { ++ dev_dbg(nor->dev, "error %d reading %x\n", ret, nor->read_opcode); ++ goto read_err; ++ } ++ ++ printf("UUID: 0x"); ++ for(i = 0; iread_opcode = read_opcode; ++ nor->addr_width = addr_width; ++ nor->read_dummy = read_dummy; ++ kfree(uuid); ++ ++ return ret; ++} ++ + static int spi_nor_hwcaps2cmd(u32 hwcaps, const int table[][2], size_t size) + { + size_t i; +@@ -4450,6 +4544,7 @@ int spi_nor_scan(struct spi_nor *nor) + nor->write = spi_nor_write_data; + nor->read_reg = spi_nor_read_reg; + nor->write_reg = spi_nor_write_reg; ++ nor->read_uuid = spi_nor_read_uuid; + + nor->setup = spi_nor_default_setup; + +--- a/include/linux/mtd/spi-nor.h ++++ b/include/linux/mtd/spi-nor.h +@@ -32,6 +32,7 @@ + #define SNOR_MFR_SPANSION CFI_MFR_AMD + #define SNOR_MFR_SST CFI_MFR_SST + #define SNOR_MFR_WINBOND 0xef /* Also used by some Spansion */ ++#define SNOR_MFR_EON CFI_MFR_EON + #define SNOR_MFR_CYPRESS 0x34 + + /* +@@ -590,6 +591,7 @@ struct spi_nor { + void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops); + int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len); + int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len); ++ int (*read_uuid)(struct spi_nor *nor); + + ssize_t (*read)(struct spi_nor *nor, loff_t from, + size_t len, u_char *read_buf); diff --git a/patch/u-boot/u-boot-filogic/100-15-cmd-sf-add-support-to-read-flash-unique-ID.patch b/patch/u-boot/u-boot-filogic/100-15-cmd-sf-add-support-to-read-flash-unique-ID.patch new file mode 100644 index 0000000000..68b2eee61e --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-15-cmd-sf-add-support-to-read-flash-unique-ID.patch @@ -0,0 +1,49 @@ +From e60939acbebd07161f3978d1c6f13123fdd2ebf2 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 11:27:02 +0800 +Subject: [PATCH 50/71] cmd: sf: add support to read flash unique ID + +This patch adds support to display unique ID from spi-nor flashes + +Signed-off-by: Weijie Gao +--- + cmd/sf.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +--- a/cmd/sf.c ++++ b/cmd/sf.c +@@ -421,6 +421,14 @@ static int do_spi_protect(int argc, char + return ret == 0 ? 0 : 1; + } + ++static int do_spi_flash_read_uuid(void) ++{ ++ int ret = 0; ++ ret = flash->read_uuid(flash); ++ ++ return ret == 0 ? 0 : 1; ++} ++ + enum { + STAGE_ERASE, + STAGE_CHECK, +@@ -615,6 +623,8 @@ static int do_spi_flash(struct cmd_tbl * + ret = do_spi_flash_erase(argc, argv); + else if (IS_ENABLED(CONFIG_SPI_FLASH_LOCK) && strcmp(cmd, "protect") == 0) + ret = do_spi_protect(argc, argv); ++ else if (strcmp(cmd, "uuid") == 0) ++ ret = do_spi_flash_read_uuid(); + else if (IS_ENABLED(CONFIG_CMD_SF_TEST) && !strcmp(cmd, "test")) + ret = do_spi_flash_test(argc, argv); + else +@@ -643,8 +653,9 @@ U_BOOT_LONGHELP(sf, + " at address 'sector'" + #endif + #ifdef CONFIG_CMD_SF_TEST +- "\nsf test offset len - run a very basic destructive test" ++ "\nsf test offset len - run a very basic destructive test" + #endif ++ "\nsf uuid - read uuid from flash" + ); + + U_BOOT_CMD( diff --git a/patch/u-boot/u-boot-filogic/100-17-common-spl-spl_nand-enable-CONFIG_SYS_NAND_U_BOOT_OF.patch b/patch/u-boot/u-boot-filogic/100-17-common-spl-spl_nand-enable-CONFIG_SYS_NAND_U_BOOT_OF.patch new file mode 100644 index 0000000000..8501105863 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-17-common-spl-spl_nand-enable-CONFIG_SYS_NAND_U_BOOT_OF.patch @@ -0,0 +1,28 @@ +From 7ab891faaaf2b6126694352d4503dc40605a6aec Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 15:10:02 +0800 +Subject: [PATCH 52/71] common: spl: spl_nand: enable + CONFIG_SYS_NAND_U_BOOT_OFFS undefined + +Enable using spl_nand with CONFIG_SYS_NAND_U_BOOT_OFFS undefined since +mtk-snand does not require raw nand framework. + +Signed-off-by: Weijie Gao +--- + common/spl/spl_nand.c | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/common/spl/spl_nand.c ++++ b/common/spl/spl_nand.c +@@ -18,7 +18,11 @@ + + uint32_t __weak spl_nand_get_uboot_raw_page(void) + { ++#ifdef CONFIG_SYS_NAND_U_BOOT_OFFS + return CONFIG_SYS_NAND_U_BOOT_OFFS; ++#else ++ return 0; ++#endif + } + + #if defined(CONFIG_SPL_NAND_RAW_ONLY) diff --git a/patch/u-boot/u-boot-filogic/100-21-mtd-spi-nor-add-more-flash-ids.patch b/patch/u-boot/u-boot-filogic/100-21-mtd-spi-nor-add-more-flash-ids.patch new file mode 100644 index 0000000000..39b254133e --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-21-mtd-spi-nor-add-more-flash-ids.patch @@ -0,0 +1,77 @@ +From a2df2df6fd1aec32572c7b30ccf5a184ec1763fd Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Wed, 27 Jul 2022 16:32:17 +0800 +Subject: [PATCH 56/71] mtd: spi-nor: add more flash ids + +Add more spi-nor flash ids + +Signed-off-by: Weijie Gao +--- + drivers/mtd/spi/spi-nor-core.c | 1 + + drivers/mtd/spi/spi-nor-ids.c | 23 ++++++++++++++++++++++- + 2 files changed, 23 insertions(+), 1 deletion(-) + +--- a/drivers/mtd/spi/spi-nor-core.c ++++ b/drivers/mtd/spi/spi-nor-core.c +@@ -758,6 +758,7 @@ static int set_4byte(struct spi_nor *nor + case SNOR_MFR_ISSI: + case SNOR_MFR_MACRONIX: + case SNOR_MFR_WINBOND: ++ case SNOR_MFR_EON: + if (need_wren) + write_enable(nor); + +--- a/drivers/mtd/spi/spi-nor-ids.c ++++ b/drivers/mtd/spi/spi-nor-ids.c +@@ -83,7 +83,9 @@ const struct flash_info spi_nor_ids[] = + { INFO("en25q32b", 0x1c3016, 0, 64 * 1024, 64, 0) }, + { INFO("en25q64", 0x1c3017, 0, 64 * 1024, 128, SECT_4K) }, + { INFO("en25q128b", 0x1c3018, 0, 64 * 1024, 256, 0) }, +- { INFO("en25qh128", 0x1c7018, 0, 64 * 1024, 256, 0) }, ++ { INFO("en25qh128", 0x1c7018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, ++ { INFO("en25qx128", 0x1c7118, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) }, ++ { INFO("en25qh256", 0x1c7019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { INFO("en25s64", 0x1c3817, 0, 64 * 1024, 128, SECT_4K) }, + #endif + #ifdef CONFIG_SPI_FLASH_GIGADEVICE /* GIGADEVICE */ +@@ -149,6 +151,11 @@ const struct flash_info spi_nor_ids[] = + {INFO("gd55x02g", 0xc8481C, 0, 64 * 1024, 4096, SECT_4K | + SPI_NOR_OCTAL_READ | SPI_NOR_4B_OPCODES)}, + { ++ INFO("gd25q256", 0xc84019, 0, 64 * 1024, 512, ++ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | ++ SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) ++ }, ++ { + INFO("gd25lq128", 0xc86018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) +@@ -520,6 +527,16 @@ const struct flash_info spi_nor_ids[] = + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { ++ INFO("w25q256jv", 0xef7019, 0, 64 * 1024, 512, ++ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | ++ SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) ++ }, ++ { ++ INFO("w25q512jv", 0xef7020, 0, 64 * 1024, 1024, ++ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | ++ SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) ++ }, ++ { + INFO("w25q128jw", 0xef8018, 0, 64 * 1024, 256, + SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) +@@ -583,6 +600,11 @@ const struct flash_info spi_nor_ids[] = + SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) + }, + { INFO("w25q256", 0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, ++ { ++ INFO("w25q512", 0xef4020, 0, 64 * 1024, 1024, ++ SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | ++ SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) ++ }, + { INFO("w25m512jw", 0xef6119, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { INFO("w25m512jv", 0xef7119, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, + { INFO("w25h02jv", 0xef9022, 0, 64 * 1024, 4096, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, diff --git a/patch/u-boot/u-boot-filogic/100-22-mtd-spi-nand-backport-from-upstream-kernel.patch b/patch/u-boot/u-boot-filogic/100-22-mtd-spi-nand-backport-from-upstream-kernel.patch new file mode 100644 index 0000000000..aa03c9b3b9 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-22-mtd-spi-nand-backport-from-upstream-kernel.patch @@ -0,0 +1,550 @@ +From 8d0665327819c41fce2c8d50f19c967b22eae564 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Wed, 27 Jul 2022 16:36:13 +0800 +Subject: [PATCH 57/71] mtd: spi-nand: backport from upstream kernel + +Backport new features from upstream kernel + +Signed-off-by: Weijie Gao +--- + drivers/mtd/nand/spi/Kconfig | 1 + + drivers/mtd/nand/spi/Makefile | 2 +- + drivers/mtd/nand/spi/core.c | 102 ++++++---- + drivers/mtd/nand/spi/etron.c | 181 +++++++++++++++++ + drivers/mtd/nand/spi/gigadevice.c | 322 ++++++++++++++++++++++++++---- + drivers/mtd/nand/spi/macronix.c | 173 +++++++++++++--- + drivers/mtd/nand/spi/micron.c | 50 ++--- + drivers/mtd/nand/spi/toshiba.c | 66 +++--- + drivers/mtd/nand/spi/winbond.c | 164 ++++++++++++--- + include/linux/mtd/spinand.h | 87 +++++--- + 10 files changed, 923 insertions(+), 225 deletions(-) + create mode 100644 drivers/mtd/nand/spi/etron.c + +--- a/drivers/mtd/nand/spi/Makefile ++++ b/drivers/mtd/nand/spi/Makefile +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0 + +-spinand-objs := core.o esmt.o gigadevice.o macronix.o micron.o paragon.o ++spinand-objs := core.o esmt.o etron.o gigadevice.o macronix.o micron.o paragon.o + spinand-objs += toshiba.o winbond.o xtx.o + obj-$(CONFIG_MTD_SPI_NAND) += spinand.o +--- a/drivers/mtd/nand/spi/core.c ++++ b/drivers/mtd/nand/spi/core.c +@@ -826,6 +826,7 @@ static const struct nand_ops spinand_ops + }; + + static const struct spinand_manufacturer *spinand_manufacturers[] = { ++ &etron_spinand_manufacturer, + &gigadevice_spinand_manufacturer, + ¯onix_spinand_manufacturer, + µn_spinand_manufacturer, +--- /dev/null ++++ b/drivers/mtd/nand/spi/etron.c +@@ -0,0 +1,181 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2020 Etron Technology, Inc. ++ * ++ */ ++#ifndef __UBOOT__ ++#include ++#include ++#include ++#endif ++#include ++#include ++ ++#define SPINAND_MFR_ETRON 0xD5 ++ ++#define STATUS_ECC_LIMIT_BITFLIPS (3 << 4) ++ ++static SPINAND_OP_VARIANTS(read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(write_cache_variants, ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(update_cache_variants, ++ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), ++ SPINAND_PROG_LOAD(false, 0, NULL, 0)); ++ ++static int etron_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (14 * section) + 72; ++ region->length = 14; ++ ++ return 0; ++} ++ ++static int etron_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ if (section) { ++ region->offset = 18 * section; ++ region->length = 18; ++ } else { ++ /* section 0 has one byte reserved for bad block mark */ ++ region->offset = 2; ++ region->length = 16; ++ } ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops etron_ooblayout = { ++ .ecc = etron_ooblayout_ecc, ++ .rfree = etron_ooblayout_free, ++}; ++ ++static int etron_ecc_get_status(struct spinand_device *spinand, ++ u8 status) ++{ ++ struct nand_device *nand = spinand_to_nand(spinand); ++ ++ switch (status & STATUS_ECC_MASK) { ++ case STATUS_ECC_NO_BITFLIPS: ++ return 0; ++ ++ case STATUS_ECC_UNCOR_ERROR: ++ return -EBADMSG; ++ ++ case STATUS_ECC_HAS_BITFLIPS: ++ return nand->eccreq.strength >> 1; ++ ++ case STATUS_ECC_LIMIT_BITFLIPS: ++ return nand->eccreq.strength; ++ ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++ ++static const struct spinand_info etron_spinand_table[] = { ++ /* EM73C 1Gb 3.3V */ ++ SPINAND_INFO("EM73C044VCF", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x25), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ /* EM7xD 2Gb */ ++ SPINAND_INFO("EM73D044VCR", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x41), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ SPINAND_INFO("EM73D044VCO", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x3A), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ SPINAND_INFO("EM78D044VCM", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x8E), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ /* EM7xE 4Gb */ ++ SPINAND_INFO("EM73E044VCE", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x3B), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ SPINAND_INFO("EM78E044VCD", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x8F), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ /* EM7xF044VCA 8Gb */ ++ SPINAND_INFO("EM73F044VCA", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), ++ NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++ SPINAND_INFO("EM78F044VCA", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x8D), ++ NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&etron_ooblayout, etron_ecc_get_status)), ++}; ++ ++static const struct spinand_manufacturer_ops etron_spinand_manuf_ops = { ++}; ++ ++const struct spinand_manufacturer etron_spinand_manufacturer = { ++ .id = SPINAND_MFR_ETRON, ++ .name = "Etron", ++ .chips = etron_spinand_table, ++ .nchips = ARRAY_SIZE(etron_spinand_table), ++ .ops = &etron_spinand_manuf_ops, ++}; +--- a/drivers/mtd/nand/spi/gigadevice.c ++++ b/drivers/mtd/nand/spi/gigadevice.c +@@ -43,6 +43,24 @@ static SPINAND_OP_VARIANTS(read_cache_va + SPINAND_PAGE_READ_FROM_CACHE_OP_3A(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP_3A(false, 0, 0, NULL, 0)); + ++/* Q5 1Gb */ ++static SPINAND_OP_VARIANTS(dummy2_read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ ++/* Q5 2Gb & 4Gb */ ++static SPINAND_OP_VARIANTS(dummy4_read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ + static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); +@@ -268,7 +286,45 @@ static int gd5fxgq4ufxxg_ecc_get_status( + return -EINVAL; + } + ++static int esmt_1_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (16 * section) + 8; ++ region->length = 8; ++ ++ return 0; ++} ++ ++static int esmt_1_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (16 * section) + 2; ++ region->length = 6; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops esmt_1_ooblayout = { ++ .ecc = esmt_1_ooblayout_ecc, ++ .rfree = esmt_1_ooblayout_free, ++ }; ++ + static const struct spinand_info gigadevice_spinand_table[] = { ++ SPINAND_INFO("F50L1G41LB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&dummy2_read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&esmt_1_ooblayout, NULL)), + SPINAND_INFO("GD5F1GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf1), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), +@@ -349,6 +405,87 @@ static const struct spinand_info gigadev + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F2GQ5UExxG", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x52), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&dummy4_read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq5xexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F4GQ6UExxG", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x55), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&dummy4_read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq5xexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F1GM7UExxG", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x91), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq4uexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F2GM7UExxG", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x92), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq4uexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F4GM8UExxG", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x95), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq4uexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F1GQ5UExxH", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x31), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&dummy2_read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq5xexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F2GQ5UExxH", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x32), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&dummy4_read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq5xexxg_ecc_get_status)), ++ SPINAND_INFO("GD5F4GQ6UExxH", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), ++ NAND_MEMORG(1, 2048, 64, 64, 4096, 40, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&dummy4_read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, ++ gd5fxgq5xexxg_ecc_get_status)), ++ + }; + + static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = { +--- a/drivers/mtd/nand/spi/winbond.c ++++ b/drivers/mtd/nand/spi/winbond.c +@@ -18,6 +18,23 @@ + + #define WINBOND_CFG_BUF_READ BIT(3) + ++#define W25N02_N04KV_STATUS_ECC_MASK (3 << 4) ++#define W25N02_N04KV_STATUS_ECC_NO_BITFLIPS (0 << 4) ++#define W25N02_N04KV_STATUS_ECC_1_4_BITFLIPS (1 << 4) ++#define W25N02_N04KV_STATUS_ECC_5_8_BITFLIPS (3 << 4) ++#define W25N02_N04KV_STATUS_ECC_UNCOR_ERROR (2 << 4) ++ ++#define W25N01_M02GV_STATUS_ECC_MASK (3 << 4) ++#define W25N01_M02GV_STATUS_ECC_NO_BITFLIPS (0 << 4) ++#define W25N01_M02GV_STATUS_ECC_1_BITFLIPS (1 << 4) ++#define W25N01_M02GV_STATUS_ECC_UNCOR_ERROR (2 << 4) ++ ++#define W25N01KV_STATUS_ECC_MASK (3 << 4) ++#define W25N01KV_STATUS_ECC_NO_BITFLIPS (0 << 4) ++#define W25N01KV_STATUS_ECC_1_3_BITFLIPS (1 << 4) ++#define W25N01KV_STATUS_ECC_4_BITFLIPS (3 << 4) ++#define W25N01KV_STATUS_ECC_UNCOR_ERROR (2 << 4) ++ + static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), +@@ -34,6 +51,35 @@ static SPINAND_OP_VARIANTS(update_cache_ + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + ++static int w25n02kv_n04kv_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (16 * section) + 64; ++ region->length = 16; ++ ++ return 0; ++} ++ ++static int w25n02kv_n04kv_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (16 * section) + 2; ++ region->length = 14; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops w25n02kv_n04kv_ooblayout = { ++ .ecc = w25n02kv_n04kv_ooblayout_ecc, ++ .rfree = w25n02kv_n04kv_ooblayout_free, ++}; ++ + static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) + { +@@ -106,6 +152,58 @@ static const struct mtd_ooblayout_ops w2 + .rfree = w25n02kv_ooblayout_free, + }; + ++static int w25n01kv_ecc_get_status(struct spinand_device *spinand, ++ u8 status) ++{ ++ switch (status & W25N01KV_STATUS_ECC_MASK) { ++ case W25N01KV_STATUS_ECC_NO_BITFLIPS: ++ return 0; ++ ++ case W25N01KV_STATUS_ECC_1_3_BITFLIPS: ++ return 3; ++ ++ case W25N01KV_STATUS_ECC_4_BITFLIPS: ++ return 4; ++ ++ case W25N01KV_STATUS_ECC_UNCOR_ERROR: ++ return -EBADMSG; ++ ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++ ++static int w25n02kv_n04kv_ecc_get_status(struct spinand_device *spinand, ++ u8 status) ++{ ++ switch (status & W25N02_N04KV_STATUS_ECC_MASK) { ++ case W25N02_N04KV_STATUS_ECC_NO_BITFLIPS: ++ return 0; ++ ++ case W25N02_N04KV_STATUS_ECC_1_4_BITFLIPS: ++ return 3; ++ ++ case W25N02_N04KV_STATUS_ECC_5_8_BITFLIPS: ++ return 4; ++ ++ /* W25N02_N04KV_use internal 8bit ECC algorithm. ++ * But the ECC strength is 4 bit requried. ++ * Return 3 if the bit bit flip count less than 5. ++ * Return 4 if the bit bit flip count more than 5 to 8. ++ */ ++ ++ case W25N02_N04KV_STATUS_ECC_UNCOR_ERROR: ++ return -EBADMSG; ++ ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++ + static int w25n02kv_ecc_get_status(struct spinand_device *spinand, + u8 status) + { +@@ -163,6 +261,15 @@ static const struct spinand_info winbond + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)), ++ SPINAND_INFO("W25N01KV", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae, 0x21), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&w25n02kv_n04kv_ooblayout, w25n01kv_ecc_get_status)), + SPINAND_INFO("W25N02KV", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), +@@ -172,6 +279,16 @@ static const struct spinand_info winbond + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), ++ SPINAND_INFO("W25N04KV", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 2, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&w25n02kv_n04kv_ooblayout, ++ w25n02kv_n04kv_ecc_get_status)), + }; + + static int winbond_spinand_init(struct spinand_device *spinand) +--- a/include/linux/mtd/spinand.h ++++ b/include/linux/mtd/spinand.h +@@ -244,6 +244,7 @@ struct spinand_manufacturer { + }; + + /* SPI NAND manufacturers */ ++extern const struct spinand_manufacturer etron_spinand_manufacturer; + extern const struct spinand_manufacturer gigadevice_spinand_manufacturer; + extern const struct spinand_manufacturer macronix_spinand_manufacturer; + extern const struct spinand_manufacturer micron_spinand_manufacturer; diff --git a/patch/u-boot/u-boot-filogic/100-23-mmc-mtk-sd-add-support-to-display-verbose-error-log.patch b/patch/u-boot/u-boot-filogic/100-23-mmc-mtk-sd-add-support-to-display-verbose-error-log.patch new file mode 100644 index 0000000000..b740bb7abd --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-23-mmc-mtk-sd-add-support-to-display-verbose-error-log.patch @@ -0,0 +1,78 @@ +From 793bed29e78cc54d989333d756fef51efaca4e56 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Tue, 26 Jul 2022 09:29:18 +0800 +Subject: [PATCH 58/71] mmc: mtk-sd: add support to display verbose error log + +Add an option to enable debug log, and also display verbose error log for +both command and data. + +Signed-off-by: Weijie Gao +--- + drivers/mmc/Kconfig | 8 ++++++++ + drivers/mmc/Makefile | 4 ++++ + drivers/mmc/mtk-sd.c | 24 +++++++++++++++--------- + 3 files changed, 27 insertions(+), 9 deletions(-) + +--- a/drivers/mmc/Kconfig ++++ b/drivers/mmc/Kconfig +@@ -868,6 +868,14 @@ config MMC_MTK + This is needed if support for any SD/SDIO/MMC devices is required. + If unsure, say N. + ++config MMC_MTK_DEBUG ++ bool "Display verbose error log" ++ default n ++ depends on MMC_MTK ++ help ++ Enable this option to allow verbose error log being displayed for ++ debugging. ++ + endif + + config FSL_SDHC_V2_3 +--- a/drivers/mmc/Makefile ++++ b/drivers/mmc/Makefile +@@ -85,3 +85,7 @@ obj-$(CONFIG_RENESAS_SDHI) += tmio-comm + obj-$(CONFIG_MMC_BCM2835) += bcm2835_sdhost.o + obj-$(CONFIG_MMC_MTK) += mtk-sd.o + obj-$(CONFIG_MMC_SDHCI_F_SDH30) += f_sdh30.o ++ ++ifdef CONFIG_MMC_MTK_DEBUG ++CFLAGS_mtk-sd.o += -DDEBUG ++endif +--- a/drivers/mmc/mtk-sd.c ++++ b/drivers/mmc/mtk-sd.c +@@ -784,18 +784,24 @@ static int msdc_ops_send_cmd(struct udev + if (cmd_ret && + !(cmd_ret == -EIO && + (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || +- cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200))) ++ cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200))) { ++ dev_dbg(dev, "MSDC start command failure with %d, cmd=%d, arg=0x%x\n", ++ cmd_ret, cmd->cmdidx, cmd->cmdarg); + return cmd_ret; +- +- if (data) { +- data_ret = msdc_start_data(host, data); +- if (cmd_ret) +- return cmd_ret; +- else +- return data_ret; + } + +- return 0; ++ if (!data) ++ return cmd_ret; ++ ++ data_ret = msdc_start_data(host, data); ++ if (cmd_ret) ++ return cmd_ret; ++ ++ if (data_ret) ++ dev_dbg(dev, "MSDC start data failure with %d, cmd=%d, arg=0x%x\n", ++ data_ret, cmd->cmdidx, cmd->cmdarg); ++ ++ return data_ret; + } + + static void msdc_set_timeout(struct msdc_host *host, u32 ns, u32 clks) diff --git a/patch/u-boot/u-boot-filogic/100-24-cmd-ubi-make-volume-find-create-remove-APIs-public.patch b/patch/u-boot/u-boot-filogic/100-24-cmd-ubi-make-volume-find-create-remove-APIs-public.patch new file mode 100644 index 0000000000..f42efe0e3f --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-24-cmd-ubi-make-volume-find-create-remove-APIs-public.patch @@ -0,0 +1,58 @@ +From dd66fc817f7ab7a4fcab9836a9251a8f64f329df Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 16:58:36 +0800 +Subject: [PATCH 59/71] cmd: ubi: make volume find/create/remove APIs public + +Export ubi_create_vol/ubi_find_volume/ubi_remove_vol to public so that they +can be used by other programs. + +Signed-off-by: Weijie Gao +--- + cmd/ubi.c | 8 ++++---- + include/ubi_uboot.h | 4 ++++ + 2 files changed, 8 insertions(+), 4 deletions(-) + +--- a/cmd/ubi.c ++++ b/cmd/ubi.c +@@ -213,8 +213,8 @@ bad: + return err; + } + +-static int ubi_create_vol(char *volume, int64_t size, int dynamic, int vol_id, +- bool skipcheck) ++int ubi_create_vol(char *volume, int64_t size, int dynamic, int vol_id, ++ bool skipcheck) + { + struct ubi_mkvol_req req; + int err; +@@ -247,7 +247,7 @@ static int ubi_create_vol(char *volume, + return ubi_create_volume(ubi, &req); + } + +-static struct ubi_volume *ubi_find_volume(char *volume) ++struct ubi_volume *ubi_find_volume(char *volume) + { + struct ubi_volume *vol; + int i; +@@ -262,7 +262,7 @@ static struct ubi_volume *ubi_find_volum + return NULL; + } + +-static int ubi_remove_vol(char *volume) ++int ubi_remove_vol(char *volume) + { + int err, reserved_pebs, i; + struct ubi_volume *vol; +--- a/include/ubi_uboot.h ++++ b/include/ubi_uboot.h +@@ -50,6 +50,10 @@ extern void ubi_exit(void); + extern int ubi_part(char *part_name, const char *vid_header_offset); + extern int ubi_volume_write(char *volume, void *buf, loff_t offset, size_t size); + extern int ubi_volume_read(char *volume, char *buf, loff_t offset, size_t size); ++extern int ubi_create_vol(char *volume, int64_t size, int dynamic, int vol_id, ++ bool skipcheck); ++extern struct ubi_volume *ubi_find_volume(char *volume); ++extern int ubi_remove_vol(char *volume); + + extern struct ubi_device *ubi_devices[]; + int cmd_ubifs_mount(char *vol_name); diff --git a/patch/u-boot/u-boot-filogic/100-25-cmd-ubi-allow-creating-volume-with-all-free-spaces.patch b/patch/u-boot/u-boot-filogic/100-25-cmd-ubi-allow-creating-volume-with-all-free-spaces.patch new file mode 100644 index 0000000000..d023b004f7 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-25-cmd-ubi-allow-creating-volume-with-all-free-spaces.patch @@ -0,0 +1,27 @@ +From f6a4130959af1e6d13d616203e42ed3c894666ad Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 17:00:00 +0800 +Subject: [PATCH 60/71] cmd: ubi: allow creating volume with all free spaces + +Allow creating volume with all free spaces by giving a negative size value. + +Signed-off-by: Weijie Gao +--- + cmd/ubi.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +--- a/cmd/ubi.c ++++ b/cmd/ubi.c +@@ -226,7 +226,11 @@ int ubi_create_vol(char *volume, int64_t + + req.vol_id = vol_id; + req.alignment = 1; +- req.bytes = size; ++ ++ if (size < 0) ++ req.bytes = ubi->avail_pebs * ubi->leb_size; ++ else ++ req.bytes = size; + + strcpy(req.name, volume); + req.name_len = strlen(volume); diff --git a/patch/u-boot/u-boot-filogic/100-26-env-ubi-add-support-to-create-environment-volume-if-.patch b/patch/u-boot/u-boot-filogic/100-26-env-ubi-add-support-to-create-environment-volume-if-.patch new file mode 100644 index 0000000000..0133e09e95 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-26-env-ubi-add-support-to-create-environment-volume-if-.patch @@ -0,0 +1,71 @@ +From fc0c70a7c6a088072d0c77e5a59d5e9b7754c6db Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 17:01:20 +0800 +Subject: [PATCH 61/71] env: ubi: add support to create environment volume if + it does not exist + +Add an option to allow environment volume being auto created if not exist. + +Signed-off-by: Weijie Gao +--- + env/Kconfig | 6 ++++++ + env/ubi.c | 20 ++++++++++++++++++++ + 2 files changed, 26 insertions(+) + +--- a/env/Kconfig ++++ b/env/Kconfig +@@ -701,6 +701,12 @@ config ENV_UBI_VOLUME_REDUND + help + Name of the redundant volume that you want to store the environment in. + ++config ENV_UBI_VOLUME_CREATE ++ bool "Create UBI volume if not exist" ++ depends on ENV_IS_IN_UBI ++ help ++ Create the UBI volume if it does not exist. ++ + config ENV_UBI_VID_OFFSET + int "ubi environment VID offset" + depends on ENV_IS_IN_UBI +--- a/env/ubi.c ++++ b/env/ubi.c +@@ -105,6 +105,18 @@ static int env_ubi_save(void) + #endif /* CONFIG_SYS_REDUNDAND_ENVIRONMENT */ + #endif /* CONFIG_CMD_SAVEENV */ + ++int __weak env_ubi_volume_create(const char *volume) ++{ ++ struct ubi_volume *vol; ++ ++ vol = ubi_find_volume((char *)volume); ++ if (vol) ++ return 0; ++ ++ return ubi_create_vol((char *)volume, CONFIG_ENV_SIZE, true, ++ UBI_VOL_NUM_AUTO, false); ++} ++ + #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT + static int env_ubi_load(void) + { +@@ -134,6 +146,10 @@ static int env_ubi_load(void) + return -EIO; + } + ++ if (IS_ENABLED(CONFIG_ENV_UBI_VOLUME_CREATE)) { ++ env_ubi_volume_create(CONFIG_ENV_UBI_VOLUME); ++ env_ubi_volume_create(CONFIG_ENV_UBI_VOLUME_REDUND); ++ } + read1_fail = ubi_volume_read(CONFIG_ENV_UBI_VOLUME, (void *)tmp_env1, 0, + CONFIG_ENV_SIZE); + if (read1_fail) +@@ -171,6 +187,9 @@ static int env_ubi_load(void) + return -EIO; + } + ++ if (IS_ENABLED(CONFIG_ENV_UBI_VOLUME_CREATE)) ++ env_ubi_volume_create(CONFIG_ENV_UBI_VOLUME); ++ + if (ubi_volume_read(CONFIG_ENV_UBI_VOLUME, buf, 0, CONFIG_ENV_SIZE)) { + printf("\n** Unable to read env from %s:%s **\n", + CONFIG_ENV_UBI_PART, CONFIG_ENV_UBI_VOLUME); diff --git a/patch/u-boot/u-boot-filogic/100-27-mtd-ubi-add-support-for-UBI-end-of-filesystem-marker.patch b/patch/u-boot/u-boot-filogic/100-27-mtd-ubi-add-support-for-UBI-end-of-filesystem-marker.patch new file mode 100644 index 0000000000..9c83e6cc25 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-27-mtd-ubi-add-support-for-UBI-end-of-filesystem-marker.patch @@ -0,0 +1,66 @@ +From 189a2fe96931ef3ea0e187c8e9bfa589c2a0ae10 Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Mon, 25 Jul 2022 17:24:56 +0800 +Subject: [PATCH 62/71] mtd: ubi: add support for UBI end-of-filesystem marker + used by OpenWrt + +Add support for UBI end-of-filesystem marker used by OpenWrt to allow +attaching a new UBI mtd partition just upgraded. + +Signed-off-by: Weijie Gao +--- + drivers/mtd/ubi/attach.c | 25 ++++++++++++++++++++++--- + drivers/mtd/ubi/ubi.h | 1 + + 2 files changed, 23 insertions(+), 3 deletions(-) + +--- a/drivers/mtd/ubi/attach.c ++++ b/drivers/mtd/ubi/attach.c +@@ -803,6 +803,13 @@ out_unlock: + return err; + } + ++static bool ec_hdr_has_eof(struct ubi_ec_hdr *ech) ++{ ++ return ech->padding1[0] == 'E' && ++ ech->padding1[1] == 'O' && ++ ech->padding1[2] == 'F'; ++} ++ + /** + * scan_peb - scan and process UBI headers of a PEB. + * @ubi: UBI device description object +@@ -833,9 +840,21 @@ static int scan_peb(struct ubi_device *u + return 0; + } + +- err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0); +- if (err < 0) +- return err; ++ if (!ai->eof_found) { ++ err = ubi_io_read_ec_hdr(ubi, pnum, ech, 0); ++ if (err < 0) ++ return err; ++ ++ if (ec_hdr_has_eof(ech)) { ++ pr_notice("UBI: EOF marker found, PEBs from %d will be erased\n", ++ pnum); ++ ai->eof_found = true; ++ } ++ } ++ ++ if (ai->eof_found) ++ err = UBI_IO_FF_BITFLIPS; ++ + switch (err) { + case 0: + break; +--- a/drivers/mtd/ubi/ubi.h ++++ b/drivers/mtd/ubi/ubi.h +@@ -746,6 +746,7 @@ struct ubi_attach_info { + int mean_ec; + uint64_t ec_sum; + int ec_count; ++ bool eof_found; + struct kmem_cache *aeb_slab_cache; + }; + diff --git a/patch/u-boot/u-boot-filogic/100-29-board-mediatek-wire-up-NMBM-support.patch b/patch/u-boot/u-boot-filogic/100-29-board-mediatek-wire-up-NMBM-support.patch new file mode 100644 index 0000000000..fc3d6b0b91 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/100-29-board-mediatek-wire-up-NMBM-support.patch @@ -0,0 +1,235 @@ +From 6792b57b3ba61ca6d69ea4a13a58bed65fc5da87 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Sun, 7 Aug 2022 04:04:46 +0200 +Subject: [PATCH] board: mediatek: wire-up NMBM support + +--- + board/mediatek/mt7622/mt7622_rfb.c | 38 +++++++++++++++++++++ + board/mediatek/mt7629/mt7629_rfb.c | 38 +++++++++++++++++++++ + board/mediatek/mt7981/mt7981_rfb.c | 52 ++++++++++++++++++++++++++++ + board/mediatek/mt7986/mt7986_rfb.c | 54 ++++++++++++++++++++++++++++++ + 4 files changed, 182 insertions(+) + +--- a/board/mediatek/mt7622/mt7622_rfb.c ++++ b/board/mediatek/mt7622/mt7622_rfb.c +@@ -9,9 +9,47 @@ + #include + #include + ++#include ++#include ++#include ++#include ++ + DECLARE_GLOBAL_DATA_PTR; + + int board_init(void) + { + return 0; + } ++ ++int board_nmbm_init(void) ++{ ++#ifdef CONFIG_ENABLE_NAND_NMBM ++ struct mtd_info *lower, *upper; ++ int ret; ++ ++ printf("\n"); ++ printf("Initializing NMBM ...\n"); ++ ++ mtd_probe_devices(); ++ ++ lower = get_mtd_device_nm("spi-nand0"); ++ if (IS_ERR(lower) || !lower) { ++ printf("Lower MTD device 'spi-nand0' not found\n"); ++ return 0; ++ } ++ ++ ret = nmbm_attach_mtd(lower, ++ NMBM_F_CREATE | NMBM_F_EMPTY_PAGE_ECC_OK, ++ CONFIG_NMBM_MAX_RATIO, ++ CONFIG_NMBM_MAX_BLOCKS, &upper); ++ ++ printf("\n"); ++ ++ if (ret) ++ return 0; ++ ++ add_mtd_device(upper); ++#endif ++ ++ return 0; ++} +--- a/board/mediatek/mt7629/mt7629_rfb.c ++++ b/board/mediatek/mt7629/mt7629_rfb.c +@@ -6,6 +6,11 @@ + #include + #include + ++#include ++#include ++#include ++#include ++ + DECLARE_GLOBAL_DATA_PTR; + + int board_init(void) +@@ -15,3 +20,36 @@ int board_init(void) + + return 0; + } ++ ++int board_nmbm_init(void) ++{ ++#ifdef CONFIG_ENABLE_NAND_NMBM ++ struct mtd_info *lower, *upper; ++ int ret; ++ ++ printf("\n"); ++ printf("Initializing NMBM ...\n"); ++ ++ mtd_probe_devices(); ++ ++ lower = get_mtd_device_nm("spi-nand0"); ++ if (IS_ERR(lower) || !lower) { ++ printf("Lower MTD device 'spi-nand0' not found\n"); ++ return 0; ++ } ++ ++ ret = nmbm_attach_mtd(lower, ++ NMBM_F_CREATE | NMBM_F_EMPTY_PAGE_ECC_OK, ++ CONFIG_NMBM_MAX_RATIO, ++ CONFIG_NMBM_MAX_BLOCKS, &upper); ++ ++ printf("\n"); ++ ++ if (ret) ++ return 0; ++ ++ add_mtd_device(upper); ++#endif ++ ++ return 0; ++} +--- a/board/mediatek/mt7981/mt7981_rfb.c ++++ b/board/mediatek/mt7981/mt7981_rfb.c +@@ -4,7 +4,57 @@ + * Author: Sam Shih + */ + ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++DECLARE_GLOBAL_DATA_PTR; ++ + int board_init(void) + { + return 0; + } ++ ++int board_late_init(void) ++{ ++ gd->env_valid = 1; //to load environment variable from persistent store ++ env_relocate(); ++ return 0; ++} ++ ++int board_nmbm_init(void) ++{ ++#ifdef CONFIG_ENABLE_NAND_NMBM ++ struct mtd_info *lower, *upper; ++ int ret; ++ ++ printf("\n"); ++ printf("Initializing NMBM ...\n"); ++ ++ mtd_probe_devices(); ++ ++ lower = get_mtd_device_nm("spi-nand0"); ++ if (IS_ERR(lower) || !lower) { ++ printf("Lower MTD device 'spi-nand0' not found\n"); ++ return 0; ++ } ++ ++ ret = nmbm_attach_mtd(lower, NMBM_F_CREATE, CONFIG_NMBM_MAX_RATIO, ++ CONFIG_NMBM_MAX_BLOCKS, &upper); ++ ++ printf("\n"); ++ ++ if (ret) ++ return 0; ++ ++ add_mtd_device(upper); ++#endif ++ ++ return 0; ++} +--- a/board/mediatek/mt7986/mt7986_rfb.c ++++ b/board/mediatek/mt7986/mt7986_rfb.c +@@ -4,7 +4,59 @@ + * Author: Sam Shih + */ + ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++DECLARE_GLOBAL_DATA_PTR; ++ + int board_init(void) + { + return 0; + } ++ ++int board_late_init(void) ++{ ++ gd->env_valid = 1; //to load environment variable from persistent store ++ env_relocate(); ++ return 0; ++} ++ ++int board_nmbm_init(void) ++{ ++#ifdef CONFIG_ENABLE_NAND_NMBM ++ struct mtd_info *lower, *upper; ++ int ret; ++ ++ printf("\n"); ++ printf("Initializing NMBM ...\n"); ++ ++ mtd_probe_devices(); ++ ++ lower = get_mtd_device_nm("spi-nand0"); ++ if (IS_ERR(lower) || !lower) { ++ printf("Lower MTD device 'spi-nand0' not found\n"); ++ return 0; ++ } ++ ++ ret = nmbm_attach_mtd(lower, ++ NMBM_F_CREATE | NMBM_F_EMPTY_PAGE_ECC_OK, ++ CONFIG_NMBM_MAX_RATIO, ++ CONFIG_NMBM_MAX_BLOCKS, &upper); ++ ++ printf("\n"); ++ ++ if (ret) ++ return 0; ++ ++ add_mtd_device(upper); ++#endif ++ ++ return 0; ++} diff --git a/patch/u-boot/u-boot-filogic/101-01-mtd-spinand-add-support-for-FORESEE-F35SQA002G.patch b/patch/u-boot/u-boot-filogic/101-01-mtd-spinand-add-support-for-FORESEE-F35SQA002G.patch new file mode 100644 index 0000000000..deeb374905 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/101-01-mtd-spinand-add-support-for-FORESEE-F35SQA002G.patch @@ -0,0 +1,149 @@ +From 49c8e854869d673df8452f24dfa8989cd0f615a8 Mon Sep 17 00:00:00 2001 +From: Martin Kurbanov +Date: Mon, 2 Oct 2023 17:04:58 +0300 +Subject: [PATCH] mtd: spinand: add support for FORESEE F35SQA002G + +Add support for FORESEE F35SQA002G SPI NAND. +Datasheet: + https://www.longsys.com/uploads/LM-00006FORESEEF35SQA002GDatasheet_1650183701.pdf + +Signed-off-by: Martin Kurbanov +Signed-off-by: Miquel Raynal +Link: https://lore.kernel.org/linux-mtd/20231002140458.147605-1-mmkurbanov@salutedevices.com +--- + drivers/mtd/nand/spi/Makefile | 2 +- + drivers/mtd/nand/spi/core.c | 1 + + drivers/mtd/nand/spi/foresee.c | 95 ++++++++++++++++++++++++++++++++++ + include/linux/mtd/spinand.h | 1 + + 4 files changed, 98 insertions(+), 1 deletion(-) + create mode 100644 drivers/mtd/nand/spi/foresee.c + +--- a/drivers/mtd/nand/spi/Makefile ++++ b/drivers/mtd/nand/spi/Makefile +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0 + +-spinand-objs := core.o esmt.o etron.o gigadevice.o macronix.o micron.o paragon.o ++spinand-objs := core.o esmt.o foresee.o etron.o gigadevice.o macronix.o micron.o paragon.o + spinand-objs += toshiba.o winbond.o xtx.o + obj-$(CONFIG_MTD_SPI_NAND) += spinand.o +--- a/drivers/mtd/nand/spi/core.c ++++ b/drivers/mtd/nand/spi/core.c +@@ -834,6 +834,7 @@ static const struct spinand_manufacturer + &toshiba_spinand_manufacturer, + &winbond_spinand_manufacturer, + &esmt_c8_spinand_manufacturer, ++ &foresee_spinand_manufacturer, + &xtx_spinand_manufacturer, + }; + +--- /dev/null ++++ b/drivers/mtd/nand/spi/foresee.c +@@ -0,0 +1,97 @@ ++// SPDX-License-Identifier: (GPL-2.0+ OR MIT) ++/* ++ * Copyright (c) 2023, SberDevices. All Rights Reserved. ++ * ++ * Author: Martin Kurbanov ++ */ ++ ++#ifndef __UBOOT__ ++#include ++#include ++#endif ++#include ++ ++#define SPINAND_MFR_FORESEE 0xCD ++ ++static SPINAND_OP_VARIANTS(read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(write_cache_variants, ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(update_cache_variants, ++ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), ++ SPINAND_PROG_LOAD(false, 0, NULL, 0)); ++ ++static int f35sqa002g_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ return -ERANGE; ++} ++ ++static int f35sqa002g_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ /* Reserve 2 bytes for the BBM. */ ++ region->offset = 2; ++ region->length = 62; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops f35sqa002g_ooblayout = { ++ .ecc = f35sqa002g_ooblayout_ecc, ++ .rfree = f35sqa002g_ooblayout_free, ++}; ++ ++static int f35sqa002g_ecc_get_status(struct spinand_device *spinand, u8 status) ++{ ++ struct nand_device *nand = spinand_to_nand(spinand); ++ ++ switch (status & STATUS_ECC_MASK) { ++ case STATUS_ECC_NO_BITFLIPS: ++ return 0; ++ ++ case STATUS_ECC_HAS_BITFLIPS: ++ return 1; ++ ++ default: ++ break; ++ } ++ ++ /* More than 1-bit error was detected in one or more sectors and ++ * cannot be corrected. ++ */ ++ return -EBADMSG; ++} ++ ++static const struct spinand_info foresee_spinand_table[] = { ++ SPINAND_INFO("F35SQA002G", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x72, 0x72), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(1, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&f35sqa002g_ooblayout, ++ f35sqa002g_ecc_get_status)), ++}; ++ ++static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = { ++}; ++ ++const struct spinand_manufacturer foresee_spinand_manufacturer = { ++ .id = SPINAND_MFR_FORESEE, ++ .name = "FORESEE", ++ .chips = foresee_spinand_table, ++ .nchips = ARRAY_SIZE(foresee_spinand_table), ++ .ops = &foresee_spinand_manuf_ops, ++}; +--- a/include/linux/mtd/spinand.h ++++ b/include/linux/mtd/spinand.h +@@ -252,6 +252,7 @@ extern const struct spinand_manufacturer + extern const struct spinand_manufacturer toshiba_spinand_manufacturer; + extern const struct spinand_manufacturer winbond_spinand_manufacturer; + extern const struct spinand_manufacturer esmt_c8_spinand_manufacturer; ++extern const struct spinand_manufacturer foresee_spinand_manufacturer; + extern const struct spinand_manufacturer xtx_spinand_manufacturer; + + /** diff --git a/patch/u-boot/u-boot-filogic/101-02-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch b/patch/u-boot/u-boot-filogic/101-02-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch new file mode 100644 index 0000000000..3ad206e399 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/101-02-mtd-spinand-add-support-for-FORESEE-F35SQA001G.patch @@ -0,0 +1,38 @@ +From ae461cde5c559675fc4c0ba351c7c31ace705f56 Mon Sep 17 00:00:00 2001 +From: Bohdan Chubuk +Date: Sun, 10 Nov 2024 22:50:47 +0200 +Subject: [PATCH] mtd: spinand: add support for FORESEE F35SQA001G + +Add support for FORESEE F35SQA001G SPI NAND. + +Similar to F35SQA002G, but differs in capacity. +Datasheet: + - https://cdn.ozdisan.com/ETicaret_Dosya/704795_871495.pdf + +Tested on Xiaomi AX3000T flashed with OpenWRT. + +Signed-off-by: Bohdan Chubuk +Signed-off-by: Miquel Raynal +--- + drivers/mtd/nand/spi/foresee.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/drivers/mtd/nand/spi/foresee.c ++++ b/drivers/mtd/nand/spi/foresee.c +@@ -83,6 +83,16 @@ static const struct spinand_info foresee + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&f35sqa002g_ooblayout, + f35sqa002g_ecc_get_status)), ++ SPINAND_INFO("F35SQA001G", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71, 0x71), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(1, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&f35sqa002g_ooblayout, ++ f35sqa002g_ecc_get_status)), + }; + + static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = { diff --git a/patch/u-boot/u-boot-filogic/101-03-mtd-spinand-fix-support-for-FORESEE.patch b/patch/u-boot/u-boot-filogic/101-03-mtd-spinand-fix-support-for-FORESEE.patch new file mode 100644 index 0000000000..48a6515b56 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/101-03-mtd-spinand-fix-support-for-FORESEE.patch @@ -0,0 +1,19 @@ +Force update_cache_variants to use reset for Foresee NAND with bad blocks + +Tested on Xiaomi AX3000T + F35SQA001G with bad blocks and without bad blocks + +Signed-off-by: Dim Fish + +--- a/drivers/mtd/nand/spi/foresee.c ++++ b/drivers/mtd/nand/spi/foresee.c +@@ -22,8 +22,8 @@ static SPINAND_OP_VARIANTS(write_cache_v + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + + static SPINAND_OP_VARIANTS(update_cache_variants, +- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), +- SPINAND_PROG_LOAD(false, 0, NULL, 0)); ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); + + static int f35sqa002g_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) diff --git a/patch/u-boot/u-boot-filogic/103-04-mt7988-enable-pstore.patch b/patch/u-boot/u-boot-filogic/103-04-mt7988-enable-pstore.patch new file mode 100644 index 0000000000..1f339d4b5b --- /dev/null +++ b/patch/u-boot/u-boot-filogic/103-04-mt7988-enable-pstore.patch @@ -0,0 +1,33 @@ +--- a/arch/arm/dts/mt7988.dtsi ++++ b/arch/arm/dts/mt7988.dtsi +@@ -63,6 +63,30 @@ + #clock-cells = <0>; + }; + ++ psci { ++ compatible = "arm,psci-0.2"; ++ method = "smc"; ++ }; ++ ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ /* 64 KiB reserved for ramoops/pstore */ ++ ramoops@42ff0000 { ++ compatible = "ramoops"; ++ reg = <0 0x42ff0000 0 0x10000>; ++ record-size = <0x1000>; ++ }; ++ ++ /* 320 KiB reserved for ARM Trusted Firmware (BL31+BL32) */ ++ secmon_reserved: secmon@43000000 { ++ reg = <0 0x43000000 0 0x50000>; ++ no-map; ++ }; ++ }; ++ + hwver: hwver { + compatible = "mediatek,hwver", "syscon"; + reg = <0 0x8000000 0 0x1000>; diff --git a/patch/u-boot/u-boot-filogic/105-configs-add-usefull-stuff-to-mt7988-rfb.patch b/patch/u-boot/u-boot-filogic/105-configs-add-usefull-stuff-to-mt7988-rfb.patch new file mode 100644 index 0000000000..085491c371 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/105-configs-add-usefull-stuff-to-mt7988-rfb.patch @@ -0,0 +1,289 @@ +--- a/configs/mt7988_sd_rfb_defconfig ++++ b/configs/mt7988_sd_rfb_defconfig +@@ -5,37 +5,76 @@ CONFIG_ARCH_MEDIATEK=y + CONFIG_TEXT_BASE=0x41e00000 + CONFIG_SYS_MALLOC_F_LEN=0x4000 + CONFIG_NR_DRAM_BANKS=1 ++CONFIG_ENV_SIZE=0x40000 ++CONFIG_ENV_OFFSET=0x400000 + CONFIG_DEFAULT_DEVICE_TREE="mt7988-sd-rfb" ++CONFIG_OF_LIBFDT_OVERLAY=y + CONFIG_TARGET_MT7988=y + CONFIG_SYS_LOAD_ADDR=0x46000000 ++CONFIG_PRE_CON_BUF_ADDR=0x4007EF00 + CONFIG_DEBUG_UART_BASE=0x11000000 + CONFIG_DEBUG_UART_CLOCK=40000000 ++CONFIG_ENV_OFFSET_REDUND=0x440000 ++CONFIG_PCI=y + CONFIG_DEBUG_UART=y + # CONFIG_EFI_LOADER is not set ++CONFIG_FIT=y ++CONFIG_BOOTSTD_FULL=y ++CONFIG_SD_BOOT=y ++CONFIG_SPI_BOOT=y + # CONFIG_AUTOBOOT is not set + CONFIG_DEFAULT_FDT_FILE="mt7988-sd-rfb" + CONFIG_SYS_CBSIZE=512 + CONFIG_SYS_PBSIZE=1049 + CONFIG_LOGLEVEL=7 ++CONFIG_PRE_CONSOLE_BUFFER=y + CONFIG_LOG=y ++CONFIG_BOARD_LATE_INIT=y + CONFIG_SYS_PROMPT="MT7988> " ++CONFIG_CMD_CPU=y ++CONFIG_CMD_LICENSE=y + # CONFIG_BOOTM_NETBSD is not set + # CONFIG_BOOTM_PLAN9 is not set + # CONFIG_BOOTM_RTEMS is not set + # CONFIG_BOOTM_VXWORKS is not set +-# CONFIG_CMD_ELF is not set ++CONFIG_CMD_BOOTMENU=y ++CONFIG_CMD_ASKENV=y ++CONFIG_CMD_ERASEENV=y ++CONFIG_CMD_ENV_FLAGS=y ++CONFIG_CMD_STRINGS=y + CONFIG_CMD_CLK=y + CONFIG_CMD_DM=y + CONFIG_CMD_GPIO=y + CONFIG_CMD_PWM=y ++CONFIG_CMD_GPT=y + CONFIG_CMD_MMC=y + CONFIG_CMD_MTD=y +-CONFIG_CMD_PING=y ++CONFIG_CMD_PCI=y ++CONFIG_CMD_USB=y ++CONFIG_CMD_TFTPSRV=y ++CONFIG_CMD_RARP=y ++CONFIG_CMD_CDP=y ++CONFIG_CMD_SNTP=y ++CONFIG_CMD_LINK_LOCAL=y ++CONFIG_CMD_DNS=y ++CONFIG_CMD_CACHE=y ++CONFIG_CMD_PSTORE=y ++CONFIG_CMD_PSTORE_MEM_ADDR=0x42ff0000 ++CONFIG_CMD_UUID=y ++CONFIG_CMD_HASH=y + CONFIG_CMD_SMC=y +-CONFIG_DOS_PARTITION=y +-CONFIG_EFI_PARTITION=y ++CONFIG_CMD_FS_UUID=y ++CONFIG_CMD_UBI=y ++CONFIG_CMD_UBI_RENAME=y + CONFIG_PARTITION_TYPE_GUID=y ++CONFIG_OF_EMBED=y ++CONFIG_ENV_OVERWRITE=y ++CONFIG_ENV_IS_IN_MMC=y ++CONFIG_SYS_REDUNDAND_ENVIRONMENT=y ++CONFIG_SYS_RELOC_GD_ENV_ADDR=y + CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y ++CONFIG_VERSION_VARIABLE=y ++CONFIG_NETCONSOLE=y + CONFIG_USE_IPADDR=y + CONFIG_IPADDR="192.168.1.1" + CONFIG_USE_NETMASK=y +@@ -44,28 +83,43 @@ CONFIG_USE_SERVERIP=y + CONFIG_SERVERIP="192.168.1.2" + CONFIG_PROT_TCP=y + CONFIG_NET_RANDOM_ETHADDR=y +-CONFIG_REGMAP=y +-CONFIG_SYSCON=y ++CONFIG_BUTTON=y ++CONFIG_BUTTON_GPIO=y + CONFIG_CLK=y ++CONFIG_GPIO_HOG=y ++CONFIG_LED=y ++CONFIG_LED_BLINK=y ++CONFIG_LED_GPIO=y + CONFIG_MMC_HS200_SUPPORT=y + CONFIG_MMC_MTK=y + CONFIG_MTD=y + CONFIG_DM_MTD=y + CONFIG_MTD_SPI_NAND=y ++CONFIG_MTD_UBI_FASTMAP=y + CONFIG_PHY_FIXED=y + CONFIG_MEDIATEK_ETH=y ++CONFIG_PCIE_MEDIATEK=y ++CONFIG_PHY=y ++CONFIG_PHY_MTK_TPHY=y + CONFIG_PINCTRL=y + CONFIG_PINCONF=y + CONFIG_PINCTRL_MT7988=y + CONFIG_POWER_DOMAIN=y + CONFIG_MTK_POWER_DOMAIN=y ++CONFIG_DM_REGULATOR=y ++CONFIG_DM_REGULATOR_FIXED=y ++CONFIG_DM_REGULATOR_GPIO=y + CONFIG_DM_PWM=y + CONFIG_PWM_MTK=y + CONFIG_RAM=y + CONFIG_DM_SERIAL=y ++CONFIG_SERIAL_RX_BUFFER=y + CONFIG_MTK_SERIAL=y + CONFIG_SPI=y + CONFIG_DM_SPI=y + CONFIG_MTK_SPIM=y +-CONFIG_LZO=y ++CONFIG_USB=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_XHCI_MTK=y ++CONFIG_ZSTD=y + CONFIG_HEXDUMP=y +--- a/configs/mt7988_rfb_defconfig ++++ b/configs/mt7988_rfb_defconfig +@@ -6,36 +6,76 @@ CONFIG_TEXT_BASE=0x41e00000 + CONFIG_SYS_MALLOC_F_LEN=0x4000 + CONFIG_NR_DRAM_BANKS=1 + CONFIG_DEFAULT_DEVICE_TREE="mt7988-rfb" ++CONFIG_OF_LIBFDT_OVERLAY=y + CONFIG_TARGET_MT7988=y + CONFIG_SYS_LOAD_ADDR=0x44000000 ++CONFIG_PRE_CON_BUF_ADDR=0x4007EF00 + CONFIG_DEBUG_UART_BASE=0x11000000 + CONFIG_DEBUG_UART_CLOCK=40000000 ++CONFIG_PCI=y + CONFIG_DEBUG_UART=y + # CONFIG_EFI_LOADER is not set +-# CONFIG_AUTOBOOT is not set ++CONFIG_FIT=y ++CONFIG_BOOTSTD_FULL=y ++CONFIG_SD_BOOT=y ++CONFIG_SPI_BOOT=y ++CONFIG_OF_SYSTEM_SETUP=y + CONFIG_DEFAULT_FDT_FILE="mt7988-rfb" + CONFIG_SYS_CBSIZE=512 + CONFIG_SYS_PBSIZE=1049 + CONFIG_LOGLEVEL=7 ++CONFIG_PRE_CONSOLE_BUFFER=y + CONFIG_LOG=y ++CONFIG_BOARD_LATE_INIT=y + CONFIG_SYS_PROMPT="MT7988> " ++CONFIG_CMD_CPU=y ++CONFIG_CMD_LICENSE=y + # CONFIG_BOOTM_NETBSD is not set + # CONFIG_BOOTM_PLAN9 is not set + # CONFIG_BOOTM_RTEMS is not set + # CONFIG_BOOTM_VXWORKS is not set +-# CONFIG_CMD_ELF is not set ++CONFIG_CMD_BOOTMENU=y ++CONFIG_CMD_ASKENV=y ++CONFIG_CMD_ERASEENV=y ++CONFIG_CMD_ENV_FLAGS=y ++CONFIG_CMD_STRINGS=y + CONFIG_CMD_CLK=y + CONFIG_CMD_DM=y + CONFIG_CMD_GPIO=y + CONFIG_CMD_PWM=y ++CONFIG_CMD_GPT=y + CONFIG_CMD_MMC=y + CONFIG_CMD_MTD=y +-CONFIG_CMD_PING=y ++CONFIG_CMD_PCI=y ++CONFIG_CMD_SF_TEST=y ++CONFIG_CMD_USB=y ++CONFIG_CMD_TFTPSRV=y ++CONFIG_CMD_RARP=y ++CONFIG_CMD_CDP=y ++CONFIG_CMD_SNTP=y ++CONFIG_CMD_LINK_LOCAL=y ++CONFIG_CMD_DNS=y ++CONFIG_CMD_CACHE=y ++CONFIG_CMD_PSTORE=y ++CONFIG_CMD_PSTORE_MEM_ADDR=0x42ff0000 ++CONFIG_CMD_UUID=y ++CONFIG_CMD_HASH=y + CONFIG_CMD_SMC=y +-CONFIG_DOS_PARTITION=y +-CONFIG_EFI_PARTITION=y ++CONFIG_CMD_FS_UUID=y ++CONFIG_CMD_UBI=y ++CONFIG_CMD_UBI_RENAME=y + CONFIG_PARTITION_TYPE_GUID=y ++CONFIG_OF_EMBED=y ++CONFIG_ENV_OVERWRITE=y ++CONFIG_ENV_IS_IN_UBI=y ++CONFIG_SYS_REDUNDAND_ENVIRONMENT=y ++CONFIG_ENV_UBI_PART="ubi" ++CONFIG_ENV_UBI_VOLUME="ubootenv" ++CONFIG_ENV_UBI_VOLUME_REDUND="ubootenv2" ++CONFIG_SYS_RELOC_GD_ENV_ADDR=y + CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y ++CONFIG_VERSION_VARIABLE=y ++CONFIG_NETCONSOLE=y + CONFIG_USE_IPADDR=y + CONFIG_IPADDR="192.168.1.1" + CONFIG_USE_NETMASK=y +@@ -44,9 +84,13 @@ CONFIG_USE_SERVERIP=y + CONFIG_SERVERIP="192.168.1.2" + CONFIG_PROT_TCP=y + CONFIG_NET_RANDOM_ETHADDR=y +-CONFIG_REGMAP=y +-CONFIG_SYSCON=y ++CONFIG_BUTTON=y ++CONFIG_BUTTON_GPIO=y + CONFIG_CLK=y ++CONFIG_GPIO_HOG=y ++CONFIG_LED=y ++CONFIG_LED_BLINK=y ++CONFIG_LED_GPIO=y + CONFIG_MMC_HS200_SUPPORT=y + CONFIG_MMC_MTK=y + CONFIG_MTD=y +@@ -64,20 +108,31 @@ CONFIG_SPI_FLASH_WINBOND=y + CONFIG_SPI_FLASH_XMC=y + CONFIG_SPI_FLASH_XTX=y + CONFIG_SPI_FLASH_MTD=y ++CONFIG_MTD_UBI_FASTMAP=y + CONFIG_PHY_FIXED=y + CONFIG_MEDIATEK_ETH=y ++CONFIG_PCIE_MEDIATEK=y ++CONFIG_PHY=y ++CONFIG_PHY_MTK_TPHY=y + CONFIG_PINCTRL=y + CONFIG_PINCONF=y + CONFIG_PINCTRL_MT7988=y + CONFIG_POWER_DOMAIN=y + CONFIG_MTK_POWER_DOMAIN=y ++CONFIG_DM_REGULATOR=y ++CONFIG_DM_REGULATOR_FIXED=y ++CONFIG_DM_REGULATOR_GPIO=y + CONFIG_DM_PWM=y + CONFIG_PWM_MTK=y + CONFIG_RAM=y + CONFIG_DM_SERIAL=y ++CONFIG_SERIAL_RX_BUFFER=y + CONFIG_MTK_SERIAL=y + CONFIG_SPI=y + CONFIG_DM_SPI=y + CONFIG_MTK_SPIM=y +-CONFIG_LZO=y ++CONFIG_USB=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_XHCI_MTK=y ++CONFIG_ZSTD=y + CONFIG_HEXDUMP=y +--- a/arch/arm/dts/mt7988-rfb.dts ++++ b/arch/arm/dts/mt7988-rfb.dts +@@ -195,6 +195,23 @@ + spi-max-frequency = <52000000>; + spi-rx-bus-width = <4>; + spi-tx-bus-width = <4>; ++ ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "bl2"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ partition@200000 { ++ label = "ubi"; ++ reg = <0x200000 0x7e00000>; ++ compatible = "linux,ubi"; ++ }; ++ }; + }; + }; + diff --git a/patch/u-boot/u-boot-filogic/120-use-xz-instead-of-lzma.patch b/patch/u-boot/u-boot-filogic/120-use-xz-instead-of-lzma.patch new file mode 100644 index 0000000000..6929453250 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/120-use-xz-instead-of-lzma.patch @@ -0,0 +1,11 @@ +--- a/Makefile ++++ b/Makefile +@@ -1094,7 +1094,7 @@ quiet_cmd_pad_cat = CAT $@ + cmd_pad_cat = $(cmd_objcopy) && $(append) || { rm -f $@; false; } + + quiet_cmd_lzma = LZMA $@ +-cmd_lzma = lzma -c -z -k -9 $< > $@ ++cmd_lzma = xz --format=lzma -c -z -k -9 $< > $@ + + cfg: u-boot.cfg + diff --git a/patch/u-boot/u-boot-filogic/130-fix-mkimage-host-build.patch b/patch/u-boot/u-boot-filogic/130-fix-mkimage-host-build.patch new file mode 100644 index 0000000000..a06935f53e --- /dev/null +++ b/patch/u-boot/u-boot-filogic/130-fix-mkimage-host-build.patch @@ -0,0 +1,24 @@ +--- a/tools/image-host.c ++++ b/tools/image-host.c +@@ -1175,6 +1175,7 @@ static int fit_config_add_verification_d + * 2) get public key (X509_get_pubkey) + * 3) provide der format (d2i_RSAPublicKey) + */ ++#ifdef CONFIG_TOOLS_LIBCRYPTO + static int read_pub_key(const char *keydir, const void *name, + unsigned char **pubkey, int *pubkey_len) + { +@@ -1228,6 +1229,13 @@ err_cert: + fclose(f); + return ret; + } ++#else ++static int read_pub_key(const char *keydir, const void *name, ++ unsigned char **pubkey, int *pubkey_len) ++{ ++ return -ENOSYS; ++} ++#endif + + int fit_pre_load_data(const char *keydir, void *keydest, void *fit) + { diff --git a/patch/u-boot/u-boot-filogic/160-net-phy-add-support-for-Airoha-ethernet-PHY-driver.patch b/patch/u-boot/u-boot-filogic/160-net-phy-add-support-for-Airoha-ethernet-PHY-driver.patch new file mode 100644 index 0000000000..d1d63d18c7 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/160-net-phy-add-support-for-Airoha-ethernet-PHY-driver.patch @@ -0,0 +1,1929 @@ +From 70157a6148ad47734f1dc646b4157ca83cc5df9f Mon Sep 17 00:00:00 2001 +From: Weijie Gao +Date: Thu, 13 Jul 2023 16:34:48 +0800 +Subject: [PATCH] net: phy: add support for Airoha ethernet PHY driver + +This patch adds support for Airoha ethernet PHY driver. + +If GMAC2 of your board connects to Airoha EN8801S, please change the eth +node as follow: + +ð { + status = "okay"; + mediatek,gmac-id = <1>; + mediatek,sgmiisys = <&sgmiisys1>; + phy-mode = "sgmii"; + phy-handle = <&phy5>; + + phy5: eth-phy@5 { + reg = <24>; + }; +}; + +If GMAC2 of your board connects to Airoha EN8811H, please change the eth +node as follow: + +ð { + status = "okay"; + mediatek,gmac-id = <1>; + mediatek,sgmiisys = <&sgmiisys1>; + phy-mode = "2500base-x"; + phy-handle = <&phy5>; + + fixed-link { + speed = <2500>; + full-duplex; + }; + + phy5: eth-phy@5 { + reg = <15>; + }; +}; + +Signed-off-by: Weijie Gao +--- + .../drivers/net/phy/Kconfig | 15 + + .../drivers/net/phy/Makefile | 2 + + .../drivers/net/phy/air_en8801s.c | 633 ++ + .../drivers/net/phy/air_en8801s.h | 267 + + .../drivers/net/phy/air_en8811h.c | 649 ++ + .../drivers/net/phy/air_en8811h.h | 160 + + .../drivers/net/phy/air_en8811h_fw.h | 9227 +++++++++++++++++ + 7 files changed, 10953 insertions(+) + create mode 100644 drivers/net/phy/air_en8801s.c + create mode 100644 drivers/net/phy/air_en8801s.h + create mode 100644 drivers/net/phy/air_en8811h.c + create mode 100644 drivers/net/phy/air_en8811h.h + create mode 100644 drivers/net/phy/air_en8811h_fw.h + +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -79,6 +79,37 @@ config PHY_ADIN + help + Add support for configuring RGMII on Analog Devices ADIN PHYs. + ++menuconfig PHY_AIROHA ++ bool "Airoha Ethernet PHYs support" ++ ++config PHY_AIROHA_EN8801S ++ bool "Airoha Ethernet EN8801S support" ++ depends on PHY_AIROHA ++ help ++ AIROHA EN8801S supported. ++ ++config PHY_AIROHA_EN8811H ++ bool "Airoha Ethernet EN8811H support" ++ depends on PHY_AIROHA ++ help ++ AIROHA EN8811H supported. ++ ++choice ++ prompt "Location of the Airoha PHY firmware" ++ default PHY_AIROHA_FW_IN_UBI ++ depends on PHY_AIROHA_EN8811H ++ ++config PHY_AIROHA_FW_IN_MMC ++ bool "Airoha firmware in MMC boot1 partition" ++ ++config PHY_AIROHA_FW_IN_UBI ++ bool "Airoha firmware in UBI volume en8811h-fw on NAND flash" ++ ++config PHY_AIROHA_FW_IN_MTD ++ bool "Airoha firmware in MTD partition on raw flash" ++ ++endchoice ++ + menuconfig PHY_AQUANTIA + bool "Aquantia Ethernet PHYs support" + select PHY_GIGE +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -11,6 +11,8 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6 + obj-$(CONFIG_PHYLIB) += phy.o + obj-$(CONFIG_PHYLIB_10G) += generic_10g.o + obj-$(CONFIG_PHY_ADIN) += adin.o ++obj-$(CONFIG_PHY_AIROHA_EN8801S) += air_en8801s.o ++obj-$(CONFIG_PHY_AIROHA_EN8811H) += air_en8811h.o + obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o + obj-$(CONFIG_PHY_ATHEROS) += atheros.o + obj-$(CONFIG_PHY_BROADCOM) += broadcom.o +--- a/drivers/net/phy/air_en8801s.c ++++ b/drivers/net/phy/air_en8801s.c +@@ -0,0 +1,633 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/************************************************* ++ * FILE NAME: air_en8801s.c ++ * PURPOSE: ++ * EN8801S PHY Driver for Uboot ++ * NOTES: ++ * ++ * Copyright (C) 2023 Airoha Technology Corp. ++ *************************************************/ ++ ++/* INCLUDE FILE DECLARATIONS ++ */ ++#include ++#include ++#include ++#include ++#include "air_en8801s.h" ++ ++#if AIR_UBOOT_REVISION > 0x202004 ++#include ++#endif ++ ++static struct phy_device *s_phydev = 0; ++/****************************************************** ++ * The following led_cfg example is for reference only. ++ * LED5 1000M/LINK/ACT (GPIO5) <-> BASE_T_LED0, ++ * LED6 10/100M/LINK/ACT (GPIO9) <-> BASE_T_LED1, ++ * LED4 100M/LINK/ACT (GPIO8) <-> BASE_T_LED2, ++ ******************************************************/ ++/* User-defined.B */ ++#define AIR_LED_SUPPORT ++#ifdef AIR_LED_SUPPORT ++static const AIR_BASE_T_LED_CFG_T led_cfg[4] = ++{ ++ /* ++ * LED Enable, GPIO, LED Polarity, LED ON, LED Blink ++ */ ++ {LED_ENABLE, 5, AIR_ACTIVE_LOW, BASE_T_LED0_ON_CFG, BASE_T_LED0_BLK_CFG}, /* BASE-T LED0 */ ++ {LED_ENABLE, 9, AIR_ACTIVE_LOW, BASE_T_LED1_ON_CFG, BASE_T_LED1_BLK_CFG}, /* BASE-T LED1 */ ++ {LED_ENABLE, 8, AIR_ACTIVE_LOW, BASE_T_LED2_ON_CFG, BASE_T_LED2_BLK_CFG}, /* BASE-T LED2 */ ++ {LED_DISABLE, 1, AIR_ACTIVE_LOW, BASE_T_LED3_ON_CFG, BASE_T_LED3_BLK_CFG} /* BASE-T LED3 */ ++}; ++static const u16 led_dur = UNIT_LED_BLINK_DURATION << AIR_LED_BLK_DUR_64M; ++#endif ++/* User-defined.E */ ++/************************************************************************ ++ * F U N C T I O N S ++ ************************************************************************/ ++/* Airoha MII read function */ ++static int airoha_cl22_read(struct mii_dev *bus, int phy_addr, int phy_register) ++{ ++ int read_data = bus->read(bus, phy_addr, MDIO_DEVAD_NONE, phy_register); ++ ++ if (read_data < 0) ++ return -EIO; ++ return read_data; ++} ++ ++/* Airoha MII write function */ ++static int airoha_cl22_write(struct mii_dev *bus, int phy_addr, int phy_register, int write_data) ++{ ++ int ret = bus->write(bus, phy_addr, MDIO_DEVAD_NONE, phy_register, write_data); ++ ++ return ret; ++} ++ ++static int airoha_cl45_write(struct phy_device *phydev, int devad, int reg, int val) ++{ ++ int ret = 0; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, val); ++ AIR_RTN_ERR(ret); ++ return ret; ++} ++ ++static int airoha_cl45_read(struct phy_device *phydev, int devad, int reg) ++{ ++ int read_data, ret; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad); ++ AIR_RTN_ERR(ret); ++ read_data = phy_read(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG); ++ if (read_data < 0) ++ return -EIO; ++ return read_data; ++} ++ ++/* EN8801 PBUS write function */ ++int airoha_pbus_write(struct mii_dev *bus, int pbus_addr, int pbus_reg, unsigned long pbus_data) ++{ ++ int ret = 0; ++ ++ ret = airoha_cl22_write(bus, pbus_addr, 0x1F, (pbus_reg >> 6)); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl22_write(bus, pbus_addr, ((pbus_reg >> 2) & 0xf), (pbus_data & 0xFFFF)); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl22_write(bus, pbus_addr, 0x10, (pbus_data >> 16)); ++ AIR_RTN_ERR(ret); ++ return ret; ++} ++ ++/* EN8801 PBUS read function */ ++unsigned long airoha_pbus_read(struct mii_dev *bus, int pbus_addr, int pbus_reg) ++{ ++ unsigned long pbus_data; ++ unsigned int pbus_data_low, pbus_data_high; ++ ++ airoha_cl22_write(bus, pbus_addr, 0x1F, (pbus_reg >> 6)); ++ pbus_data_low = airoha_cl22_read(bus, pbus_addr, ((pbus_reg >> 2) & 0xf)); ++ pbus_data_high = airoha_cl22_read(bus, pbus_addr, 0x10); ++ pbus_data = (pbus_data_high << 16) + pbus_data_low; ++ return pbus_data; ++} ++ ++/* Airoha Token Ring Write function */ ++static int airoha_tr_reg_write(struct phy_device *phydev, unsigned long tr_address, unsigned long tr_data) ++{ ++ int ret; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, 0x52b5); /* page select */ ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x11, (int)(tr_data & 0xffff)); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x12, (int)(tr_data >> 16)); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (int)(tr_address | TrReg_WR)); ++ AIR_RTN_ERR(ret); ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, 0x0); /* page resetore */ ++ AIR_RTN_ERR(ret); ++ return ret; ++} ++ ++int airoha_phy_process(void) ++{ ++ int ret = 0, pbus_addr = EN8801S_PBUS_PHY_ID; ++ unsigned long pbus_data; ++ struct mii_dev *mbus; ++ ++ mbus = s_phydev->bus; ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x19e0); ++ pbus_data |= BIT(0); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x19e0, pbus_data); ++ if(ret) ++ printf("error: airoha_pbus_write fail ret: %d\n", ret); ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x19e0); ++ pbus_data &= ~BIT(0); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x19e0, pbus_data); ++ if(ret) ++ printf("error: airoha_pbus_write fail ret: %d\n", ret); ++ ++ if(ret) ++ printf("error: FCM regs reset fail, ret: %d\n", ret); ++ else ++ debug("FCM regs reset successful\n"); ++ return ret; ++} ++ ++#ifdef AIR_LED_SUPPORT ++static int airoha_led_set_usr_def(struct phy_device *phydev, u8 entity, int polar, ++ u16 on_evt, u16 blk_evt) ++{ ++ int ret = 0; ++ ++ if (AIR_ACTIVE_HIGH == polar) { ++ on_evt |= LED_ON_POL; ++ } else { ++ on_evt &= ~LED_ON_POL; ++ } ++ ret = airoha_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), on_evt | LED_ON_EN); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1f, LED_BLK_CTRL(entity), blk_evt); ++ AIR_RTN_ERR(ret); ++ return 0; ++} ++ ++static int airoha_led_set_mode(struct phy_device *phydev, u8 mode) ++{ ++ u16 cl45_data; ++ int err = 0; ++ ++ cl45_data = airoha_cl45_read(phydev, 0x1f, LED_BCR); ++ switch (mode) { ++ case AIR_LED_MODE_DISABLE: ++ cl45_data &= ~LED_BCR_EXT_CTRL; ++ cl45_data &= ~LED_BCR_MODE_MASK; ++ cl45_data |= LED_BCR_MODE_DISABLE; ++ break; ++ case AIR_LED_MODE_USER_DEFINE: ++ cl45_data |= LED_BCR_EXT_CTRL; ++ cl45_data |= LED_BCR_CLK_EN; ++ break; ++ default: ++ printf("LED mode%d is not supported!\n", mode); ++ return -EINVAL; ++ } ++ err = airoha_cl45_write(phydev, 0x1f, LED_BCR, cl45_data); ++ AIR_RTN_ERR(err); ++ return 0; ++} ++ ++static int airoha_led_set_state(struct phy_device *phydev, u8 entity, u8 state) ++{ ++ u16 cl45_data; ++ int err; ++ ++ cl45_data = airoha_cl45_read(phydev, 0x1f, LED_ON_CTRL(entity)); ++ if (LED_ENABLE == state) { ++ cl45_data |= LED_ON_EN; ++ } else { ++ cl45_data &= ~LED_ON_EN; ++ } ++ ++ err = airoha_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), cl45_data); ++ AIR_RTN_ERR(err); ++ return 0; ++} ++ ++static int en8801s_led_init(struct phy_device *phydev) ++{ ++ ++ unsigned long led_gpio = 0, reg_value = 0; ++ int ret = 0, led_id; ++ struct mii_dev *mbus = phydev->bus; ++ int gpio_led_rg[3] = {0x1870, 0x1874, 0x1878}; ++ u16 cl45_data = led_dur; ++ ++ ret = airoha_cl45_write(phydev, 0x1f, LED_BLK_DUR, cl45_data); ++ AIR_RTN_ERR(ret); ++ cl45_data >>= 1; ++ ret = airoha_cl45_write(phydev, 0x1f, LED_ON_DUR, cl45_data); ++ AIR_RTN_ERR(ret); ++ ret = airoha_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE); ++ if (ret != 0) { ++ printf("LED fail to set mode, ret %d !\n", ret); ++ return ret; ++ } ++ for(led_id = 0; led_id < EN8801S_LED_COUNT; led_id++) { ++ reg_value = 0; ++ ret = airoha_led_set_state(phydev, led_id, led_cfg[led_id].en); ++ if (ret != 0) { ++ printf("LED fail to set state, ret %d !\n", ret); ++ return ret; ++ } ++ if (LED_ENABLE == led_cfg[led_id].en) { ++ if ( (led_cfg[led_id].gpio < 0) || led_cfg[led_id].gpio > 9) { ++ printf("GPIO%d is out of range!! GPIO number is 0~9.\n", led_cfg[led_id].gpio); ++ return -EIO; ++ } ++ led_gpio |= BIT(led_cfg[led_id].gpio); ++ reg_value = airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, gpio_led_rg[led_cfg[led_id].gpio / 4]); ++ LED_SET_GPIO_SEL(led_cfg[led_id].gpio, led_id, reg_value); ++ debug("[Airoha] gpio%d, reg_value 0x%lx\n", led_cfg[led_id].gpio, reg_value); ++ ret = airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, gpio_led_rg[led_cfg[led_id].gpio / 4], reg_value); ++ AIR_RTN_ERR(ret); ++ ret = airoha_led_set_usr_def(phydev, led_id, led_cfg[led_id].pol, led_cfg[led_id].on_cfg, led_cfg[led_id].blk_cfg); ++ if (ret != 0) { ++ printf("LED fail to set usr def, ret %d !\n", ret); ++ return ret; ++ } ++ } ++ } ++ reg_value = (airoha_pbus_read(mbus, EN8801S_PBUS_PHY_ID, 0x1880) & ~led_gpio); ++ ret = airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x1880, reg_value); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, EN8801S_PBUS_PHY_ID, 0x186c, led_gpio); ++ AIR_RTN_ERR(ret); ++ ++ printf("LED initialize OK !\n"); ++ return 0; ++} ++#endif /* AIR_LED_SUPPORT */ ++ ++static int en8801s_config(struct phy_device *phydev) ++{ ++ int reg_value = 0, ret = 0; ++ struct mii_dev *mbus = phydev->bus; ++ int retry, pbus_addr = EN8801S_PBUS_DEFAULT_ID; ++ int phy_addr = EN8801S_MDIO_PHY_ID; ++ unsigned long pbus_data = 0; ++ gephy_all_REG_LpiReg1Ch GPHY_RG_LPI_1C; ++ gephy_all_REG_dev1Eh_reg324h GPHY_RG_1E_324; ++ gephy_all_REG_dev1Eh_reg012h GPHY_RG_1E_012; ++ gephy_all_REG_dev1Eh_reg017h GPHY_RG_1E_017; ++ ++ s_phydev = phydev; ++ retry = MAX_OUI_CHECK; ++ while (1) { ++ /* PHY OUI */ ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_ETHER_PHY_OUI); ++ if (EN8801S_PBUS_OUI == pbus_data) { ++ printf("PBUS addr 0x%x: Start initialized.\n", pbus_addr); ++ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_BUCK_CTL, 0x03); ++ AIR_RTN_ERR(ret); ++ break; ++ } else ++ pbus_addr = EN8801S_PBUS_PHY_ID; ++ ++ if (0 == --retry) { ++ printf("EN8801S Probe fail !\n"); ++ return 0; ++ } ++ } ++ ++ /* SMI ADDR */ ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_SMI_ADDR); ++ pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(pbus_addr << 8) | (unsigned long)(EN8801S_MDIO_DEFAULT_ID); ++ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_SMI_ADDR, pbus_data); ++ AIR_RTN_ERR(ret); ++ mdelay(10); ++ ++ pbus_data = (airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_LTR_CTL) & (~0x3)) | BIT(2) ; ++ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_LTR_CTL, pbus_data); ++ AIR_RTN_ERR(ret); ++ mdelay(500); ++ pbus_data = (pbus_data & ~BIT(2)) | EN8801S_RX_POLARITY_NORMAL | EN8801S_TX_POLARITY_NORMAL; ++ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_LTR_CTL, pbus_data); ++ AIR_RTN_ERR(ret); ++ mdelay(500); ++ /* SMI ADDR */ ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, EN8801S_RG_SMI_ADDR); ++ pbus_data = (pbus_data & 0xffff0000) | (unsigned long)(EN8801S_PBUS_PHY_ID << 8) | (unsigned long)(EN8801S_MDIO_PHY_ID); ++ ret = airoha_pbus_write(mbus, pbus_addr, EN8801S_RG_SMI_ADDR, pbus_data); ++ pbus_addr = EN8801S_PBUS_PHY_ID; ++ AIR_RTN_ERR(ret); ++ mdelay(10); ++ ++ /* Optimze 10M IoT */ ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1690); ++ pbus_data |= (1 << 31); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1690, pbus_data); ++ AIR_RTN_ERR(ret); ++ /* set SGMII Base Page */ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD801); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x0003); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00); ++ AIR_RTN_ERR(ret); ++ /* Set FCM control */ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x004b); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007); ++ AIR_RTN_ERR(ret); ++ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x142c, 0x05050505); ++ AIR_RTN_ERR(ret); ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1440); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1440, pbus_data & ~BIT(11)); ++ AIR_RTN_ERR(ret); ++ ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1408); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1408, pbus_data | BIT(5)); ++ AIR_RTN_ERR(ret); ++ ++ /* Set GPHY Perfomance*/ ++ /* Token Ring */ ++ ret = airoha_tr_reg_write(phydev, RgAddr_R1000DEC_15h, 0x0055A0); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_R1000DEC_17h, 0x07FF3F); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_00h, 0x00001E); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_01h, 0x6FB90A); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_17h, 0x060671); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_PMA_18h, 0x0E2F00); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_TR_26h, 0x444444); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_03h, 0x000000); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_06h, 0x2EBAEF); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_08h, 0x00000B); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_0Ch, 0x00504D); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_0Dh, 0x02314F); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_0Fh, 0x003028); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_10h, 0x005010); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_11h, 0x040001); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_13h, 0x018670); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_14h, 0x00024A); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_1Bh, 0x000072); ++ AIR_RTN_ERR(ret); ++ ret = airoha_tr_reg_write(phydev, RgAddr_DSPF_1Ch, 0x003210); ++ AIR_RTN_ERR(ret); ++ /* CL22 & CL45 */ ++ ret = airoha_cl22_write(mbus, phy_addr, 0x1f, 0x03); ++ AIR_RTN_ERR(ret); ++ GPHY_RG_LPI_1C.DATA = airoha_cl22_read(mbus, phy_addr, RgAddr_LPI_1Ch); ++ if (GPHY_RG_LPI_1C.DATA < 0) ++ return -EIO; ++ GPHY_RG_LPI_1C.DataBitField.smi_deton_th = 0x0C; ++ ret = airoha_cl22_write(mbus, phy_addr, RgAddr_LPI_1Ch, GPHY_RG_LPI_1C.DATA); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl22_write(mbus, phy_addr, RgAddr_LPI_1Ch, 0xC92); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl22_write(mbus, phy_addr, RgAddr_AUXILIARY_1Dh, 0x1); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl22_write(mbus, phy_addr, 0x1f, 0x0); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x120, 0x8014); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x122, 0xffff); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x123, 0xffff); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x144, 0x0200); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x14A, 0xEE20); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x189, 0x0110); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x19B, 0x0111); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x234, 0x0181); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x238, 0x0120); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x239, 0x0117); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x268, 0x07F4); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x2D1, 0x0733); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x323, 0x0011); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x324, 0x013F); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x326, 0x0037); ++ AIR_RTN_ERR(ret); ++ ++ reg_value = airoha_cl45_read(phydev, 0x1E, 0x324); ++ if (reg_value < 0) ++ return -EIO; ++ GPHY_RG_1E_324.DATA = (int)reg_value; ++ GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = 0; ++ ret = airoha_cl45_write(phydev, 0x1E, 0x324, GPHY_RG_1E_324.DATA); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x19E, 0xC2); ++ AIR_RTN_ERR(ret); ++ ret = airoha_cl45_write(phydev, 0x1E, 0x013, 0x0); ++ AIR_RTN_ERR(ret); ++ ++ /* EFUSE */ ++ airoha_pbus_write(mbus, pbus_addr, 0x1C08, 0x40000040); ++ retry = MAX_RETRY; ++ while (0 != retry) { ++ mdelay(1); ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1C08); ++ if ((pbus_data & (1 << 30)) == 0) { ++ break; ++ } ++ retry--; ++ } ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1C38); /* RAW#2 */ ++ reg_value = airoha_cl45_read(phydev, 0x1E, 0x12); ++ if (reg_value < 0) ++ return -EIO; ++ GPHY_RG_1E_012.DATA = reg_value; ++ GPHY_RG_1E_012.DataBitField.da_tx_i2mpb_a_tbt = pbus_data & 0x03f; ++ ret = airoha_cl45_write(phydev, 0x1E, 0x12, GPHY_RG_1E_012.DATA); ++ AIR_RTN_ERR(ret); ++ reg_value = airoha_cl45_read(phydev, 0x1E, 0x17); ++ if (reg_value < 0) ++ return -EIO; ++ GPHY_RG_1E_017.DataBitField.da_tx_i2mpb_b_tbt = (reg_value >> 8) & 0x03f; ++ ret = airoha_cl45_write(phydev, 0x1E, 0x17, GPHY_RG_1E_017.DATA); ++ AIR_RTN_ERR(ret); ++ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1C08, 0x40400040); ++ AIR_RTN_ERR(ret); ++ retry = MAX_RETRY; ++ while (0 != retry) { ++ mdelay(1); ++ reg_value = airoha_pbus_read(mbus, pbus_addr, 0x1C08); ++ if ((reg_value & (1 << 30)) == 0) { ++ break; ++ } ++ retry--; ++ } ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1C30); /* RAW#16 */ ++ GPHY_RG_1E_324.DataBitField.smi_det_deglitch_off = (pbus_data >> 12) & 0x01; ++ ret = airoha_cl45_write(phydev, 0x1E, 0x324, GPHY_RG_1E_324.DATA); ++ AIR_RTN_ERR(ret); ++#ifdef AIR_LED_SUPPORT ++ ret = en8801s_led_init(phydev); ++ if (ret != 0){ ++ printf("en8801s_led_init fail (ret:%d) !\n", ret); ++ } ++#endif ++ printf("EN8801S initialize OK ! (%s)\n", EN8801S_DRIVER_VERSION); ++ return 0; ++} ++ ++int en8801s_read_status(struct phy_device *phydev) ++{ ++ int ret, pbus_addr = EN8801S_PBUS_PHY_ID; ++ struct mii_dev *mbus; ++ unsigned long pbus_data; ++ ++ mbus = phydev->bus; ++ if (SPEED_10 == phydev->speed) { ++ /* set the bit for Optimze 10M IoT */ ++ debug("[Airoha] SPEED_10 0x1694\n"); ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1694); ++ pbus_data |= (1 << 31); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1694, pbus_data); ++ AIR_RTN_ERR(ret); ++ } else { ++ debug("[Airoha] SPEED_1000/100 0x1694\n"); ++ /* clear the bit for other speeds */ ++ pbus_data = airoha_pbus_read(mbus, pbus_addr, 0x1694); ++ pbus_data &= ~(1 << 31); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1694, pbus_data); ++ AIR_RTN_ERR(ret); ++ } ++ ++ airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00); ++ if(SPEED_1000 == phydev->speed) { ++ debug("[Airoha] SPEED_1000\n"); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD801); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140); ++ AIR_RTN_ERR(ret); ++ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x0003); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c000c00); ++ AIR_RTN_ERR(ret); ++ mdelay(2); /* delay 2 ms */ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x004b); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007); ++ AIR_RTN_ERR(ret); ++ } ++ else if (SPEED_100 == phydev->speed) { ++ debug("[Airoha] SPEED_100\n"); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD401); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x0007); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c11); ++ AIR_RTN_ERR(ret); ++ mdelay(2); /* delay 2 ms */ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x0027); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007); ++ AIR_RTN_ERR(ret); ++ } ++ else { ++ debug("[Airoha] SPEED_10\n"); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x10, 0xD001); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0, 0x9140); ++ AIR_RTN_ERR(ret); ++ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0A14, 0x000b); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x0600, 0x0c11); ++ AIR_RTN_ERR(ret); ++ mdelay(2); /* delay 2 ms */ ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x1404, 0x0047); ++ AIR_RTN_ERR(ret); ++ ret = airoha_pbus_write(mbus, pbus_addr, 0x140c, 0x0007); ++ AIR_RTN_ERR(ret); ++ } ++ return 0; ++} ++ ++static int en8801s_startup(struct phy_device *phydev) ++{ ++ int ret; ++ ++ ret = genphy_update_link(phydev); ++ if (ret) ++ return ret; ++ ret = genphy_parse_link(phydev); ++ if (ret) ++ return ret; ++ return en8801s_read_status(phydev); ++} ++#if AIR_UBOOT_REVISION > 0x202303 ++U_BOOT_PHY_DRIVER(en8801s) = { ++ .name = "Airoha EN8801S", ++ .uid = EN8801S_PHY_ID, ++ .mask = 0x0ffffff0, ++ .features = PHY_GBIT_FEATURES, ++ .config = &en8801s_config, ++ .startup = &en8801s_startup, ++ .shutdown = &genphy_shutdown, ++}; ++#else ++static struct phy_driver AIR_EN8801S_driver = { ++ .name = "Airoha EN8801S", ++ .uid = EN8801S_PHY_ID, ++ .mask = 0x0ffffff0, ++ .features = PHY_GBIT_FEATURES, ++ .config = &en8801s_config, ++ .startup = &en8801s_startup, ++ .shutdown = &genphy_shutdown, ++}; ++ ++int phy_air_en8801s_init(void) ++{ ++ phy_register(&AIR_EN8801S_driver); ++ return 0; ++} ++#endif +--- a/drivers/net/phy/air_en8801s.h ++++ b/drivers/net/phy/air_en8801s.h +@@ -0,0 +1,267 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/************************************************* ++ * FILE NAME: air_en8801s.h ++ * PURPOSE: ++ * EN8801S PHY Driver for Uboot ++ * NOTES: ++ * ++ * Copyright (C) 2023 Airoha Technology Corp. ++ *************************************************/ ++ ++#ifndef __EN8801S_H ++#define __EN8801S_H ++ ++/************************************************************************ ++* D E F I N E S ++************************************************************************/ ++#define AIR_UBOOT_REVISION ((((U_BOOT_VERSION_NUM / 1000) % 10) << 20) | \ ++ (((U_BOOT_VERSION_NUM / 100) % 10) << 16) | \ ++ (((U_BOOT_VERSION_NUM / 10) % 10) << 12) | \ ++ ((U_BOOT_VERSION_NUM % 10) << 8) | \ ++ (((U_BOOT_VERSION_NUM_PATCH / 10) % 10) << 4) | \ ++ ((U_BOOT_VERSION_NUM_PATCH % 10) << 0)) ++ ++#define EN8801S_MDIO_DEFAULT_ID 0x1d ++#define EN8801S_PBUS_DEFAULT_ID (EN8801S_MDIO_DEFAULT_ID + 1) ++#define EN8801S_MDIO_PHY_ID 0x18 /* Range PHY_ADDRESS_RANGE .. 0x1e */ ++#define EN8801S_PBUS_PHY_ID (EN8801S_MDIO_PHY_ID + 1) ++#define EN8801S_DRIVER_VERSION "v1.1.3" ++ ++#define EN8801S_RG_ETHER_PHY_OUI 0x19a4 ++#define EN8801S_RG_SMI_ADDR 0x19a8 ++#define EN8801S_PBUS_OUI 0x17a5 ++#define EN8801S_RG_BUCK_CTL 0x1a20 ++#define EN8801S_RG_LTR_CTL 0x0cf8 ++ ++#define EN8801S_PHY_ID1 0x03a2 ++#define EN8801S_PHY_ID2 0x9461 ++#define EN8801S_PHY_ID (unsigned long)((EN8801S_PHY_ID1 << 16) | EN8801S_PHY_ID2) ++ ++/* ++SFP Sample for verification ++Tx Reverse, Rx Reverse ++*/ ++#define EN8801S_TX_POLARITY_NORMAL 0x0 ++#define EN8801S_TX_POLARITY_REVERSE 0x1 ++ ++#define EN8801S_RX_POLARITY_NORMAL (0x1 << 1) ++#define EN8801S_RX_POLARITY_REVERSE (0x0 << 1) ++ ++#ifndef BIT ++#define BIT(nr) (1UL << (nr)) ++#endif ++ ++#define MAX_RETRY 5 ++#define MAX_OUI_CHECK 2 ++ ++/* CL45 MDIO control */ ++#define MII_MMD_ACC_CTL_REG 0x0d ++#define MII_MMD_ADDR_DATA_REG 0x0e ++#define MMD_OP_MODE_DATA BIT(14) ++ ++#define MAX_TRG_COUNTER 5 ++ ++/* TokenRing Reg Access */ ++#define TrReg_PKT_XMT_STA 0x8000 ++#define TrReg_WR 0x8000 ++#define TrReg_RD 0xA000 ++ ++#define RgAddr_LPI_1Ch 0x1c ++#define RgAddr_AUXILIARY_1Dh 0x1d ++#define RgAddr_PMA_00h 0x0f80 ++#define RgAddr_PMA_01h 0x0f82 ++#define RgAddr_PMA_17h 0x0fae ++#define RgAddr_PMA_18h 0x0fb0 ++#define RgAddr_DSPF_03h 0x1686 ++#define RgAddr_DSPF_06h 0x168c ++#define RgAddr_DSPF_08h 0x1690 ++#define RgAddr_DSPF_0Ch 0x1698 ++#define RgAddr_DSPF_0Dh 0x169a ++#define RgAddr_DSPF_0Fh 0x169e ++#define RgAddr_DSPF_10h 0x16a0 ++#define RgAddr_DSPF_11h 0x16a2 ++#define RgAddr_DSPF_13h 0x16a6 ++#define RgAddr_DSPF_14h 0x16a8 ++#define RgAddr_DSPF_1Bh 0x16b6 ++#define RgAddr_DSPF_1Ch 0x16b8 ++#define RgAddr_TR_26h 0x0ecc ++#define RgAddr_R1000DEC_15h 0x03aa ++#define RgAddr_R1000DEC_17h 0x03ae ++ ++/* ++The following led_cfg example is for reference only. ++LED5 1000M/LINK/ACT (GPIO5) <-> BASE_T_LED0, ++LED6 10/100M/LINK/ACT(GPIO9) <-> BASE_T_LED1, ++LED4 100M/LINK/ACT (GPIO8) <-> BASE_T_LED2, ++*/ ++/* User-defined.B */ ++#define BASE_T_LED0_ON_CFG (LED_ON_EVT_LINK_1000M) ++#define BASE_T_LED0_BLK_CFG (LED_BLK_EVT_1000M_TX_ACT | LED_BLK_EVT_1000M_RX_ACT) ++#define BASE_T_LED1_ON_CFG (LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M) ++#define BASE_T_LED1_BLK_CFG (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT | \ ++ LED_BLK_EVT_10M_TX_ACT | LED_BLK_EVT_10M_RX_ACT ) ++#define BASE_T_LED2_ON_CFG (LED_ON_EVT_LINK_100M) ++#define BASE_T_LED2_BLK_CFG (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT) ++#define BASE_T_LED3_ON_CFG (0x0) ++#define BASE_T_LED3_BLK_CFG (0x0) ++/* User-defined.E */ ++ ++#define EN8801S_LED_COUNT 4 ++ ++#define LED_BCR (0x021) ++#define LED_BCR_EXT_CTRL (1 << 15) ++#define LED_BCR_CLK_EN (1 << 3) ++#define LED_BCR_TIME_TEST (1 << 2) ++#define LED_BCR_MODE_MASK (3) ++#define LED_BCR_MODE_DISABLE (0) ++#define LED_ON_CTRL(i) (0x024 + ((i)*2)) ++#define LED_ON_EN (1 << 15) ++#define LED_ON_POL (1 << 14) ++#define LED_ON_EVT_MASK (0x7f) ++/* LED ON Event Option.B */ ++#define LED_ON_EVT_FORCE (1 << 6) ++#define LED_ON_EVT_LINK_DOWN (1 << 3) ++#define LED_ON_EVT_LINK_10M (1 << 2) ++#define LED_ON_EVT_LINK_100M (1 << 1) ++#define LED_ON_EVT_LINK_1000M (1 << 0) ++/* LED ON Event Option.E */ ++#define LED_BLK_CTRL(i) (0x025 + ((i)*2)) ++#define LED_BLK_EVT_MASK (0x3ff) ++/* LED Blinking Event Option.B*/ ++#define LED_BLK_EVT_FORCE (1 << 9) ++#define LED_BLK_EVT_10M_RX_ACT (1 << 5) ++#define LED_BLK_EVT_10M_TX_ACT (1 << 4) ++#define LED_BLK_EVT_100M_RX_ACT (1 << 3) ++#define LED_BLK_EVT_100M_TX_ACT (1 << 2) ++#define LED_BLK_EVT_1000M_RX_ACT (1 << 1) ++#define LED_BLK_EVT_1000M_TX_ACT (1 << 0) ++/* LED Blinking Event Option.E*/ ++#define LED_ON_DUR (0x022) ++#define LED_ON_DUR_MASK (0xffff) ++#define LED_BLK_DUR (0x023) ++#define LED_BLK_DUR_MASK (0xffff) ++ ++#define LED_ENABLE 1 ++#define LED_DISABLE 0 ++ ++#define UNIT_LED_BLINK_DURATION 1024 ++ ++#define AIR_RTN_ON_ERR(cond, err) \ ++ do { if ((cond)) return (err); } while(0) ++ ++#define AIR_RTN_ERR(err) AIR_RTN_ON_ERR(err < 0, err) ++ ++#define LED_SET_EVT(reg, cod, result, bit) do \ ++ { \ ++ if(reg & cod) { \ ++ result |= bit; \ ++ } \ ++ } while(0) ++ ++#define LED_SET_GPIO_SEL(gpio, led, val) do \ ++ { \ ++ val |= (led << (8 * (gpio % 4))); \ ++ } while(0) ++ ++/* DATA TYPE DECLARATIONS ++ */ ++typedef struct ++{ ++ int DATA_Lo; ++ int DATA_Hi; ++}TR_DATA_T; ++ ++typedef union ++{ ++ struct ++ { ++ /* b[15:00] */ ++ int smi_deton_wt : 3; ++ int smi_det_mdi_inv : 1; ++ int smi_detoff_wt : 3; ++ int smi_sigdet_debouncing_en : 1; ++ int smi_deton_th : 6; ++ int rsv_14 : 2; ++ } DataBitField; ++ int DATA; ++} gephy_all_REG_LpiReg1Ch, *Pgephy_all_REG_LpiReg1Ch; ++ ++typedef union ++{ ++ struct ++ { ++ /* b[15:00] */ ++ int rg_smi_detcnt_max : 6; ++ int rsv_6 : 2; ++ int rg_smi_det_max_en : 1; ++ int smi_det_deglitch_off : 1; ++ int rsv_10 : 6; ++ } DataBitField; ++ int DATA; ++} gephy_all_REG_dev1Eh_reg324h, *Pgephy_all_REG_dev1Eh_reg324h; ++ ++typedef union ++{ ++ struct ++ { ++ /* b[15:00] */ ++ int da_tx_i2mpb_a_tbt : 6; ++ int rsv_6 : 4; ++ int da_tx_i2mpb_a_gbe : 6; ++ } DataBitField; ++ int DATA; ++} gephy_all_REG_dev1Eh_reg012h, *Pgephy_all_REG_dev1Eh_reg012h; ++ ++typedef union ++{ ++ struct ++ { ++ /* b[15:00] */ ++ int da_tx_i2mpb_b_tbt : 6; ++ int rsv_6 : 2; ++ int da_tx_i2mpb_b_gbe : 6; ++ int rsv_14 : 2; ++ } DataBitField; ++ int DATA; ++} gephy_all_REG_dev1Eh_reg017h, *Pgephy_all_REG_dev1Eh_reg017h; ++ ++typedef struct AIR_BASE_T_LED_CFG_S ++{ ++ u16 en; ++ u16 gpio; ++ u16 pol; ++ u16 on_cfg; ++ u16 blk_cfg; ++}AIR_BASE_T_LED_CFG_T; ++ ++typedef enum ++{ ++ AIR_LED_BLK_DUR_32M, ++ AIR_LED_BLK_DUR_64M, ++ AIR_LED_BLK_DUR_128M, ++ AIR_LED_BLK_DUR_256M, ++ AIR_LED_BLK_DUR_512M, ++ AIR_LED_BLK_DUR_1024M, ++ AIR_LED_BLK_DUR_LAST ++} AIR_LED_BLK_DUT_T; ++ ++typedef enum ++{ ++ AIR_ACTIVE_LOW, ++ AIR_ACTIVE_HIGH, ++} AIR_LED_POLARITY; ++typedef enum ++{ ++ AIR_LED_MODE_DISABLE, ++ AIR_LED_MODE_USER_DEFINE, ++ AIR_LED_MODE_LAST ++} AIR_LED_MODE_T; ++ ++/************************************************************************ ++* F U N C T I O N P R O T O T Y P E S ++************************************************************************/ ++ ++unsigned long airoha_pbus_read(struct mii_dev *bus, int pbus_addr, int pbus_reg); ++int airoha_pbus_write(struct mii_dev *bus, int pbus_addr, int pbus_reg, unsigned long pbus_data); ++int airoha_phy_process(void); ++#endif /* __EN8801S_H */ +--- a/drivers/net/phy/air_en8811h.c ++++ b/drivers/net/phy/air_en8811h.c +@@ -0,0 +1,725 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/************************************************* ++ * FILE NAME: air_en8811h.c ++ * PURPOSE: ++ * EN8811H PHY Driver for Uboot ++ * NOTES: ++ * ++ * Copyright (C) 2023 Airoha Technology Corp. ++ *************************************************/ ++ ++/* INCLUDE FILE DECLARATIONS ++*/ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "air_en8811h.h" ++ ++#ifdef CONFIG_PHY_AIROHA_FW_IN_UBI ++#include ++#endif ++ ++#ifdef CONFIG_PHY_AIROHA_FW_IN_MMC ++#include ++#endif ++ ++#ifdef CONFIG_PHY_AIROHA_FW_IN_MTD ++#include ++#endif ++ ++#if AIR_UBOOT_REVISION > 0x202004 ++#include ++#endif ++ ++/************************** ++ * GPIO5 <-> BASE_T_LED0, ++ * GPIO4 <-> BASE_T_LED1, ++ * GPIO3 <-> BASE_T_LED2, ++ **************************/ ++/* User-defined.B */ ++#define AIR_LED_SUPPORT ++#ifdef AIR_LED_SUPPORT ++static const struct air_base_t_led_cfg_s led_cfg[3] = { ++/********************************************************************* ++ *Enable, GPIO, LED Polarity, LED ON, LED Blink ++**********************************************************************/ ++ {1, AIR_LED0_GPIO5, AIR_ACTIVE_HIGH, AIR_LED0_ON, AIR_LED0_BLK}, ++ {1, AIR_LED1_GPIO4, AIR_ACTIVE_HIGH, AIR_LED1_ON, AIR_LED1_BLK}, ++ {1, AIR_LED2_GPIO3, AIR_ACTIVE_HIGH, AIR_LED2_ON, AIR_LED2_BLK}, ++}; ++static const u16 led_dur = UNIT_LED_BLINK_DURATION << AIR_LED_BLK_DUR_64M; ++#endif ++/* User-defined.E */ ++/************************************************************* ++ * F U N C T I O N S ++ **************************************************************/ ++/* Airoha MII read function */ ++static int air_mii_cl22_read(struct mii_dev *bus, int phy_addr, int phy_register) ++{ ++ int read_data = bus->read(bus, phy_addr, MDIO_DEVAD_NONE, phy_register); ++ ++ if (read_data < 0) ++ return -EIO; ++ return read_data; ++} ++ ++/* Airoha MII write function */ ++static int air_mii_cl22_write(struct mii_dev *bus, int phy_addr, int phy_register, int write_data) ++{ ++ int ret = 0; ++ ++ ret = bus->write(bus, phy_addr, MDIO_DEVAD_NONE, phy_register, write_data); ++ if (ret < 0) { ++ printf("bus->write, ret: %d\n", ret); ++ return ret; ++ } ++ return ret; ++} ++ ++static int air_mii_cl45_read(struct phy_device *phydev, int devad, u16 reg) ++{ ++ int ret = 0; ++ int data; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return INVALID_DATA; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return INVALID_DATA; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return INVALID_DATA; ++ } ++ data = phy_read(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG); ++ return data; ++} ++ ++static int air_mii_cl45_write(struct phy_device *phydev, int devad, u16 reg, u16 write_data) ++{ ++ int ret = 0; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, devad); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, reg); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ACC_CTL_REG, MMD_OP_MODE_DATA | devad); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_MMD_ADDR_DATA_REG, write_data); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ return 0; ++} ++/* Use default PBUS_PHY_ID */ ++/* EN8811H PBUS write function */ ++static int air_pbus_reg_write(struct phy_device *phydev, unsigned long pbus_address, unsigned long pbus_data) ++{ ++ int ret = 0; ++ struct mii_dev *mbus = phydev->bus; ++ ++ ret = air_mii_cl22_write(mbus, ((phydev->addr) + 8), 0x1F, (unsigned int)(pbus_address >> 6)); ++ if (ret < 0) ++ return ret; ++ ret = air_mii_cl22_write(mbus, ((phydev->addr) + 8), (unsigned int)((pbus_address >> 2) & 0xf), (unsigned int)(pbus_data & 0xFFFF)); ++ if (ret < 0) ++ return ret; ++ ret = air_mii_cl22_write(mbus, ((phydev->addr) + 8), 0x10, (unsigned int)(pbus_data >> 16)); ++ if (ret < 0) ++ return ret; ++ return 0; ++} ++ ++/* EN8811H BUCK write function */ ++static int air_buckpbus_reg_write(struct phy_device *phydev, unsigned long pbus_address, unsigned int pbus_data) ++{ ++ int ret = 0; ++ ++ /* page 4 */ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)4); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (unsigned int)0); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x11, (unsigned int)((pbus_address >> 16) & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x12, (unsigned int)(pbus_address & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x13, (unsigned int)((pbus_data >> 16) & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x14, (unsigned int)(pbus_data & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ return 0; ++} ++ ++/* EN8811H BUCK read function */ ++static unsigned int air_buckpbus_reg_read(struct phy_device *phydev, unsigned long pbus_address) ++{ ++ unsigned int pbus_data = 0, pbus_data_low, pbus_data_high; ++ int ret = 0; ++ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)4); /* page 4 */ ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return PBUS_INVALID_DATA; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (unsigned int)0); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return PBUS_INVALID_DATA; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x15, (unsigned int)((pbus_address >> 16) & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return PBUS_INVALID_DATA; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x16, (unsigned int)(pbus_address & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return PBUS_INVALID_DATA; ++ } ++ ++ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, 0x17); ++ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, 0x18); ++ pbus_data = (pbus_data_high << 16) + pbus_data_low; ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)0); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ return pbus_data; ++} ++ ++static int MDIOWriteBuf(struct phy_device *phydev, unsigned long address, unsigned long array_size, const unsigned char *buffer) ++{ ++ unsigned int write_data, offset ; ++ int ret = 0; ++ ++ /* page 4 */ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)4); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ /* address increment*/ ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x10, (unsigned int)0x8000); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x11, (unsigned int)((address >> 16) & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x12, (unsigned int)(address & 0xffff)); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ ++ for (offset = 0; offset < array_size; offset += 4) { ++ write_data = (buffer[offset + 3] << 8) | buffer[offset + 2]; ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x13, write_data); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ write_data = (buffer[offset + 1] << 8) | buffer[offset]; ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x14, write_data); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ } ++ ret = phy_write(phydev, MDIO_DEVAD_NONE, 0x1F, (unsigned int)0); ++ if (ret < 0) { ++ printf("phy_write, ret: %d\n", ret); ++ return ret; ++ } ++ return 0; ++} ++ ++#ifdef AIR_LED_SUPPORT ++static int airoha_led_set_usr_def(struct phy_device *phydev, u8 entity, int polar, ++ u16 on_evt, u16 blk_evt) ++{ ++ int ret = 0; ++ ++ if (AIR_ACTIVE_HIGH == polar) ++ on_evt |= LED_ON_POL; ++ else ++ on_evt &= ~LED_ON_POL; ++ ++ ret = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), on_evt | LED_ON_EN); ++ if (ret < 0) ++ return ret; ++ ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_CTRL(entity), blk_evt); ++ if (ret < 0) ++ return ret; ++ return 0; ++} ++ ++static int airoha_led_set_mode(struct phy_device *phydev, u8 mode) ++{ ++ u16 cl45_data; ++ int err = 0; ++ ++ cl45_data = air_mii_cl45_read(phydev, 0x1f, LED_BCR); ++ switch (mode) { ++ case AIR_LED_MODE_DISABLE: ++ cl45_data &= ~LED_BCR_EXT_CTRL; ++ cl45_data &= ~LED_BCR_MODE_MASK; ++ cl45_data |= LED_BCR_MODE_DISABLE; ++ break; ++ case AIR_LED_MODE_USER_DEFINE: ++ cl45_data |= LED_BCR_EXT_CTRL; ++ cl45_data |= LED_BCR_CLK_EN; ++ break; ++ default: ++ printf("LED mode%d is not supported!\n", mode); ++ return -EINVAL; ++ } ++ err = air_mii_cl45_write(phydev, 0x1f, LED_BCR, cl45_data); ++ if (err < 0) ++ return err; ++ return 0; ++} ++ ++static int airoha_led_set_state(struct phy_device *phydev, u8 entity, u8 state) ++{ ++ u16 cl45_data; ++ int err; ++ ++ cl45_data = air_mii_cl45_read(phydev, 0x1f, LED_ON_CTRL(entity)); ++ if (LED_ENABLE == state) ++ cl45_data |= LED_ON_EN; ++ else ++ cl45_data &= ~LED_ON_EN; ++ ++ err = air_mii_cl45_write(phydev, 0x1f, LED_ON_CTRL(entity), cl45_data); ++ if (err < 0) ++ return err; ++ return 0; ++} ++ ++static int en8811h_led_init(struct phy_device *phydev) ++{ ++ unsigned int led_gpio = 0, reg_value = 0; ++ u16 cl45_data = led_dur; ++ int ret, led_id; ++ ++ cl45_data = UNIT_LED_BLINK_DURATION << AIR_LED_BLK_DUR_64M; ++ ret = air_mii_cl45_write(phydev, 0x1f, LED_BLK_DUR, cl45_data); ++ if (ret < 0) ++ return ret; ++ cl45_data >>= 1; ++ ret = air_mii_cl45_write(phydev, 0x1f, LED_ON_DUR, cl45_data); ++ if (ret < 0) ++ return ret; ++ ++ ret = airoha_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE); ++ if (ret != 0) { ++ printf("LED fail to set mode, ret %d !\n", ret); ++ return ret; ++ } ++ for(led_id = 0; led_id < EN8811H_LED_COUNT; led_id++) ++ { ++ /* LED0 <-> GPIO5, LED1 <-> GPIO4, LED0 <-> GPIO3 */ ++ if ( led_cfg[led_id].gpio != (led_id + (AIR_LED0_GPIO5 - (2 * led_id)))) { ++ printf("LED%d uses incorrect GPIO%d !\n", led_id, led_cfg[led_id].gpio); ++ return -EINVAL; ++ } ++ reg_value = 0; ++ if (led_cfg[led_id].en == LED_ENABLE) ++ { ++ led_gpio |= BIT(led_cfg[led_id].gpio); ++ ret = airoha_led_set_state(phydev, led_id, led_cfg[led_id].en); ++ if (ret != 0) { ++ printf("LED fail to set state, ret %d !\n", ret); ++ return ret; ++ } ++ ret = airoha_led_set_usr_def(phydev, led_id, led_cfg[led_id].pol, led_cfg[led_id].on_cfg, led_cfg[led_id].blk_cfg); ++ if (ret != 0) { ++ printf("LED fail to set default, ret %d !\n", ret); ++ return ret; ++ } ++ } ++ } ++ ret = air_buckpbus_reg_write(phydev, 0xcf8b8, led_gpio); ++ if (ret < 0) ++ return ret; ++ printf("LED initialize OK !\n"); ++ return 0; ++} ++#endif /* AIR_LED_SUPPORT */ ++ ++static char *firmware_buf; ++static int en8811h_load_firmware(struct phy_device *phydev) ++{ ++ u32 pbus_value; ++ int ret = 0; ++ ++ if (!firmware_buf) { ++ firmware_buf = malloc(EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE); ++ if (!firmware_buf) { ++ printf("[Airoha] cannot allocated buffer for firmware.\n"); ++ return -ENOMEM; ++ } ++ ++#ifdef CONFIG_PHY_AIROHA_FW_IN_UBI ++ ret = ubi_volume_read("en8811h-fw", firmware_buf, 0, EN8811H_MD32_DM_SIZE + EN8811H_MD32_DSP_SIZE); ++ if (ret) { ++ printf("[Airoha] read firmware from UBI failed.\n"); ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return ret; ++ } ++#elif defined(CONFIG_PHY_AIROHA_FW_IN_MMC) ++ struct mmc *mmc = find_mmc_device(0); ++ if (!mmc) { ++ printf("[Airoha] opening MMC device failed.\n"); ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return -ENODEV; ++ } ++ if (mmc_init(mmc)) { ++ printf("[Airoha] initializing MMC device failed.\n"); ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return -ENODEV; ++ } ++ if (IS_SD(mmc)) { ++ printf("[Airoha] SD card is not supported.\n"); ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return -EINVAL; ++ } ++ ret = mmc_set_part_conf(mmc, 1, 2, 2); ++ if (ret) { ++ printf("[Airoha] cannot access eMMC boot1 hw partition.\n"); ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return ret; ++ } ++ ret = blk_dread(mmc_get_blk_desc(mmc), 0, 0x120, firmware_buf); ++ mmc_set_part_conf(mmc, 1, 1, 0); ++ if (ret != 0x120) { ++ printf("[Airoha] cannot read firmware from eMMC.\n"); ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return -EIO; ++ } ++#else ++#warning EN8811H firmware loading not implemented ++ free(firmware_buf); ++ firmware_buf = NULL; ++ return -EOPNOTSUPP; ++#endif ++ } ++ ++ ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x0); ++ if (ret < 0) ++ return ret; ++ pbus_value = air_buckpbus_reg_read(phydev, 0x800000); ++ pbus_value |= BIT(11); ++ ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value); ++ if (ret < 0) ++ return ret; ++ /* Download DM */ ++ ret = MDIOWriteBuf(phydev, 0x00000000, EN8811H_MD32_DM_SIZE, firmware_buf); ++ if (ret < 0) { ++ printf("[Airoha] MDIOWriteBuf 0x00000000 fail.\n"); ++ return ret; ++ } ++ /* Download PM */ ++ ret = MDIOWriteBuf(phydev, 0x00100000, EN8811H_MD32_DSP_SIZE, firmware_buf + EN8811H_MD32_DM_SIZE); ++ if (ret < 0) { ++ printf("[Airoha] MDIOWriteBuf 0x00100000 fail.\n"); ++ return ret; ++ } ++ pbus_value = air_buckpbus_reg_read(phydev, 0x800000); ++ pbus_value &= ~BIT(11); ++ ret = air_buckpbus_reg_write(phydev, 0x800000, pbus_value); ++ if (ret < 0) ++ return ret; ++ ret = air_buckpbus_reg_write(phydev, 0x0f0018, 0x01); ++ if (ret < 0) ++ return ret; ++ return 0; ++} ++ ++static int en8811h_config(struct phy_device *phydev) ++{ ++ int ret = 0; ++ int pid1 = 0, pid2 = 0; ++ ++ ret = air_pbus_reg_write(phydev, 0xcf928 , 0x0); ++ if (ret < 0) ++ return ret; ++ ++ pid1 = phy_read(phydev, MDIO_DEVAD_NONE, MII_PHYSID1); ++ pid2 = phy_read(phydev, MDIO_DEVAD_NONE, MII_PHYSID2); ++ if ((EN8811H_PHY_ID1 != pid1) || (EN8811H_PHY_ID2 != pid2)) { ++ printf("EN8811H does not exist !\n"); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static int en8811h_get_autonego(struct phy_device *phydev, int *an) ++{ ++ int reg; ++ reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); ++ if (reg < 0) ++ return -EINVAL; ++ if (reg & BMCR_ANENABLE) ++ *an = AUTONEG_ENABLE; ++ else ++ *an = AUTONEG_DISABLE; ++ return 0; ++} ++ ++static int en8811h_startup(struct phy_device *phydev) ++{ ++ ofnode node = phy_get_ofnode(phydev); ++ int ret = 0, lpagb = 0, lpa = 0, common_adv_gb = 0, common_adv = 0, advgb = 0, adv = 0, reg = 0, an = AUTONEG_DISABLE, bmcr = 0, reg_value; ++ int old_link = phydev->link; ++ u32 pbus_value = 0, retry; ++ ++ eth_phy_reset(phydev->dev, 1); ++ mdelay(10); ++ eth_phy_reset(phydev->dev, 0); ++ mdelay(1); ++ ++ ret = en8811h_load_firmware(phydev); ++ if (ret) { ++ printf("EN8811H load firmware fail.\n"); ++ return ret; ++ } ++ retry = MAX_RETRY; ++ do { ++ mdelay(300); ++ reg_value = air_mii_cl45_read(phydev, 0x1e, 0x8009); ++ if (EN8811H_PHY_READY == reg_value) { ++ printf("EN8811H PHY ready!\n"); ++ break; ++ } ++ retry--; ++ } while (retry); ++ if (0 == retry) { ++ printf("EN8811H PHY is not ready. (MD32 FW Status reg: 0x%x)\n", reg_value); ++ pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c); ++ printf("Check MD32 FW Version(0x3b3c) : %08x\n", pbus_value); ++ printf("EN8811H initialize fail!\n"); ++ return 0; ++ } ++ /* Mode selection*/ ++ printf("EN8811H Mode 1 !\n"); ++ ret = air_mii_cl45_write(phydev, 0x1e, 0x800c, 0x0); ++ if (ret < 0) ++ return ret; ++ ret = air_mii_cl45_write(phydev, 0x1e, 0x800d, 0x0); ++ if (ret < 0) ++ return ret; ++ ret = air_mii_cl45_write(phydev, 0x1e, 0x800e, 0x1101); ++ if (ret < 0) ++ return ret; ++ ret = air_mii_cl45_write(phydev, 0x1e, 0x800f, 0x0002); ++ if (ret < 0) ++ return ret; ++ ++ /* Serdes polarity */ ++ pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8); ++ pbus_value &= 0xfffffffc; ++ pbus_value |= ofnode_read_bool(node, "airoha,rx-pol-reverse") ? ++ EN8811H_RX_POLARITY_REVERSE : EN8811H_RX_POLARITY_NORMAL; ++ pbus_value |= ofnode_read_bool(node, "airoha,tx-pol-reverse") ? ++ EN8811H_TX_POLARITY_REVERSE : EN8811H_TX_POLARITY_NORMAL; ++ ret = air_buckpbus_reg_write(phydev, 0xca0f8, pbus_value); ++ if (ret < 0) ++ return ret; ++ pbus_value = air_buckpbus_reg_read(phydev, 0xca0f8); ++ printf("Tx, Rx Polarity(0xca0f8): %08x\n", pbus_value); ++ pbus_value = air_buckpbus_reg_read(phydev, 0x3b3c); ++ printf("MD32 FW Version(0x3b3c) : %08x\n", pbus_value); ++#if defined(AIR_LED_SUPPORT) ++ ret = en8811h_led_init(phydev); ++ if (ret < 0) { ++ printf("en8811h_led_init fail\n"); ++ } ++#endif ++ printf("EN8811H initialize OK ! (%s)\n", EN8811H_DRIVER_VERSION); ++ ++ ret = genphy_update_link(phydev); ++ if (ret) ++ { ++ printf("ret %d!\n", ret); ++ return ret; ++ } ++ ++ ret = genphy_parse_link(phydev); ++ if (ret) ++ { ++ printf("ret %d!\n", ret); ++ return ret; ++ } ++ ++ if (old_link && phydev->link) ++ return 0; ++ ++ phydev->speed = SPEED_100; ++ phydev->duplex = DUPLEX_FULL; ++ phydev->pause = 0; ++ phydev->asym_pause = 0; ++ ++ reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); ++ if (reg < 0) ++ { ++ printf("MII_BMSR reg %d!\n", reg); ++ return reg; ++ } ++ reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); ++ if (reg < 0) ++ { ++ printf("MII_BMSR reg %d!\n", reg); ++ return reg; ++ } ++ if(reg & BMSR_LSTATUS) ++ { ++ pbus_value = air_buckpbus_reg_read(phydev, 0x109D4); ++ if (0x10 & pbus_value) { ++ phydev->speed = SPEED_2500; ++ phydev->duplex = DUPLEX_FULL; ++ } ++ else ++ { ++ ret = en8811h_get_autonego(phydev, &an); ++ if ((AUTONEG_ENABLE == an) && (0 == ret)) ++ { ++ printf("AN mode!\n"); ++ printf("SPEED 1000/100!\n"); ++ lpagb = phy_read(phydev, MDIO_DEVAD_NONE, MII_STAT1000); ++ if (lpagb < 0 ) ++ return lpagb; ++ advgb = phy_read(phydev, MDIO_DEVAD_NONE, MII_CTRL1000); ++ if (adv < 0 ) ++ return adv; ++ common_adv_gb = (lpagb & (advgb << 2)); ++ ++ lpa = phy_read(phydev, MDIO_DEVAD_NONE, MII_LPA); ++ if (lpa < 0 ) ++ return lpa; ++ adv = phy_read(phydev, MDIO_DEVAD_NONE, MII_ADVERTISE); ++ if (adv < 0 ) ++ return adv; ++ common_adv = (lpa & adv); ++ ++ phydev->speed = SPEED_10; ++ phydev->duplex = DUPLEX_HALF; ++ if (common_adv_gb & (LPA_1000FULL | LPA_1000HALF)) ++ { ++ phydev->speed = SPEED_1000; ++ if (common_adv_gb & LPA_1000FULL) ++ ++ phydev->duplex = DUPLEX_FULL; ++ } ++ else if (common_adv & (LPA_100FULL | LPA_100HALF)) ++ { ++ phydev->speed = SPEED_100; ++ if (common_adv & LPA_100FULL) ++ phydev->duplex = DUPLEX_FULL; ++ } ++ else ++ { ++ if (common_adv & LPA_10FULL) ++ phydev->duplex = DUPLEX_FULL; ++ } ++ } ++ else ++ { ++ printf("Force mode!\n"); ++ bmcr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); ++ ++ if (bmcr < 0) ++ return bmcr; ++ ++ if (bmcr & BMCR_FULLDPLX) ++ phydev->duplex = DUPLEX_FULL; ++ else ++ phydev->duplex = DUPLEX_HALF; ++ ++ if (bmcr & BMCR_SPEED1000) ++ phydev->speed = SPEED_1000; ++ else if (bmcr & BMCR_SPEED100) ++ phydev->speed = SPEED_100; ++ else ++ phydev->speed = SPEED_100; ++ } ++ } ++ } ++ ++ return ret; ++} ++ ++#if AIR_UBOOT_REVISION > 0x202303 ++U_BOOT_PHY_DRIVER(en8811h) = { ++ .name = "Airoha EN8811H", ++ .uid = EN8811H_PHY_ID, ++ .mask = 0x0ffffff0, ++ .config = &en8811h_config, ++ .startup = &en8811h_startup, ++ .shutdown = &genphy_shutdown, ++}; ++#else ++static struct phy_driver AIR_EN8811H_driver = { ++ .name = "Airoha EN8811H", ++ .uid = EN8811H_PHY_ID, ++ .mask = 0x0ffffff0, ++ .config = &en8811h_config, ++ .startup = &en8811h_startup, ++ .shutdown = &genphy_shutdown, ++}; ++ ++int phy_air_en8811h_init(void) ++{ ++ phy_register(&AIR_EN8811H_driver); ++ return 0; ++} ++#endif +--- a/drivers/net/phy/air_en8811h.h ++++ b/drivers/net/phy/air_en8811h.h +@@ -0,0 +1,163 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/************************************************* ++ * FILE NAME: air_en8811h.h ++ * PURPOSE: ++ * EN8811H PHY Driver for Uboot ++ * NOTES: ++ * ++ * Copyright (C) 2023 Airoha Technology Corp. ++ *************************************************/ ++ ++#ifndef __EN8811H_H ++#define __EN8811H_H ++ ++#define AIR_UBOOT_REVISION ((((U_BOOT_VERSION_NUM / 1000) % 10) << 20) | \ ++ (((U_BOOT_VERSION_NUM / 100) % 10) << 16) | \ ++ (((U_BOOT_VERSION_NUM / 10) % 10) << 12) | \ ++ ((U_BOOT_VERSION_NUM % 10) << 8) | \ ++ (((U_BOOT_VERSION_NUM_PATCH / 10) % 10) << 4) | \ ++ ((U_BOOT_VERSION_NUM_PATCH % 10) << 0)) ++ ++#define EN8811H_PHY_ID1 0x03a2 ++#define EN8811H_PHY_ID2 0xa411 ++#define EN8811H_PHY_ID ((EN8811H_PHY_ID1 << 16) | EN8811H_PHY_ID2) ++#define EN8811H_SPEED_2500 0x03 ++#define EN8811H_PHY_READY 0x02 ++#define MAX_RETRY 5 ++ ++#define EN8811H_MD32_DM_SIZE 0x4000 ++#define EN8811H_MD32_DSP_SIZE 0x20000 ++ ++#define EN8811H_TX_POLARITY_NORMAL 0x1 ++#define EN8811H_TX_POLARITY_REVERSE 0x0 ++ ++#define EN8811H_RX_POLARITY_NORMAL (0x0 << 1) ++#define EN8811H_RX_POLARITY_REVERSE (0x1 << 1) ++ ++#ifndef BIT ++#define BIT(nr) (1UL << (nr)) ++#endif ++ ++/* CL45 MDIO control */ ++#define MII_MMD_ACC_CTL_REG 0x0d ++#define MII_MMD_ADDR_DATA_REG 0x0e ++#define MMD_OP_MODE_DATA BIT(14) ++/* MultiGBASE-T AN register */ ++#define MULTIG_ANAR_2500M (0x0080) ++#define MULTIG_LPAR_2500M (0x0020) ++ ++#define EN8811H_DRIVER_VERSION "v1.0.4" ++ ++/************************************************************ ++ * For reference only ++ * LED0 Link 2500/Blink 2500 TxRx (GPIO5) <-> BASE_T_LED0, ++ * LED1 Link 1000/Blink 1000 TxRx (GPIO4) <-> BASE_T_LED1, ++ * LED2 Link 100/Blink 100 TxRx (GPIO3) <-> BASE_T_LED2, ++ ************************************************************/ ++/* User-defined.B */ ++#define AIR_LED0_ON (LED_ON_EVT_LINK_2500M) ++#define AIR_LED0_BLK (LED_BLK_EVT_2500M_TX_ACT | LED_BLK_EVT_2500M_RX_ACT) ++#define AIR_LED1_ON (LED_ON_EVT_LINK_1000M) ++#define AIR_LED1_BLK (LED_BLK_EVT_1000M_TX_ACT | LED_BLK_EVT_1000M_RX_ACT) ++#define AIR_LED2_ON (LED_ON_EVT_LINK_100M) ++#define AIR_LED2_BLK (LED_BLK_EVT_100M_TX_ACT | LED_BLK_EVT_100M_RX_ACT) ++/* User-defined.E */ ++ ++#define LED_ON_CTRL(i) (0x024 + ((i)*2)) ++#define LED_ON_EN (1 << 15) ++#define LED_ON_POL (1 << 14) ++#define LED_ON_EVT_MASK (0x1ff) ++/* LED ON Event Option.B */ ++#define LED_ON_EVT_LINK_2500M (1 << 8) ++#define LED_ON_EVT_FORCE (1 << 6) ++#define LED_ON_EVT_HDX (1 << 5) ++#define LED_ON_EVT_FDX (1 << 4) ++#define LED_ON_EVT_LINK_DOWN (1 << 3) ++#define LED_ON_EVT_LINK_100M (1 << 1) ++#define LED_ON_EVT_LINK_1000M (1 << 0) ++/* LED ON Event Option.E */ ++ ++#define LED_BLK_CTRL(i) (0x025 + ((i)*2)) ++#define LED_BLK_EVT_MASK (0xfff) ++/* LED Blinking Event Option.B*/ ++#define LED_BLK_EVT_2500M_RX_ACT (1 << 11) ++#define LED_BLK_EVT_2500M_TX_ACT (1 << 10) ++#define LED_BLK_EVT_FORCE (1 << 9) ++#define LED_BLK_EVT_100M_RX_ACT (1 << 3) ++#define LED_BLK_EVT_100M_TX_ACT (1 << 2) ++#define LED_BLK_EVT_1000M_RX_ACT (1 << 1) ++#define LED_BLK_EVT_1000M_TX_ACT (1 << 0) ++/* LED Blinking Event Option.E*/ ++#define LED_ENABLE 1 ++#define LED_DISABLE 0 ++ ++#define EN8811H_LED_COUNT 3 ++ ++#define LED_BCR (0x021) ++#define LED_BCR_EXT_CTRL (1 << 15) ++#define LED_BCR_CLK_EN (1 << 3) ++#define LED_BCR_TIME_TEST (1 << 2) ++#define LED_BCR_MODE_MASK (3) ++#define LED_BCR_MODE_DISABLE (0) ++#define LED_BCR_MODE_2LED (1) ++#define LED_BCR_MODE_3LED_1 (2) ++#define LED_BCR_MODE_3LED_2 (3) ++ ++#define LED_ON_DUR (0x022) ++#define LED_ON_DUR_MASK (0xffff) ++ ++#define LED_BLK_DUR (0x023) ++#define LED_BLK_DUR_MASK (0xffff) ++ ++#define LED_GPIO_SEL_MASK 0x7FFFFFF ++ ++#define UNIT_LED_BLINK_DURATION 1024 ++ ++#define INVALID_DATA 0xffff ++#define PBUS_INVALID_DATA 0xffffffff ++ ++struct air_base_t_led_cfg_s { ++ u16 en; ++ u16 gpio; ++ u16 pol; ++ u16 on_cfg; ++ u16 blk_cfg; ++}; ++ ++enum { ++ AIR_LED2_GPIO3 = 3, ++ AIR_LED1_GPIO4, ++ AIR_LED0_GPIO5, ++ AIR_LED_LAST ++}; ++ ++enum { ++ AIR_BASE_T_LED0, ++ AIR_BASE_T_LED1, ++ AIR_BASE_T_LED2, ++ AIR_BASE_T_LED3 ++}; ++ ++enum { ++ AIR_LED_BLK_DUR_32M, ++ AIR_LED_BLK_DUR_64M, ++ AIR_LED_BLK_DUR_128M, ++ AIR_LED_BLK_DUR_256M, ++ AIR_LED_BLK_DUR_512M, ++ AIR_LED_BLK_DUR_1024M, ++ AIR_LED_BLK_DUR_LAST ++}; ++ ++enum { ++ AIR_ACTIVE_LOW, ++ AIR_ACTIVE_HIGH, ++}; ++ ++enum { ++ AIR_LED_MODE_DISABLE, ++ AIR_LED_MODE_USER_DEFINE, ++ AIR_LED_MODE_LAST ++}; ++ ++#endif /* End of __EN8811H_MD32_H */ ++ +--- a/drivers/net/eth-phy-uclass.c ++++ b/drivers/net/eth-phy-uclass.c +@@ -154,7 +154,7 @@ static int eth_phy_of_to_plat(struct ude + return 0; + } + +-static void eth_phy_reset(struct udevice *dev, int value) ++void eth_phy_reset(struct udevice *dev, int value) + { + struct eth_phy_device_priv *uc_priv = dev_get_uclass_priv(dev); + u32 delay; +--- a/include/eth_phy.h ++++ b/include/eth_phy.h +@@ -14,5 +14,6 @@ int eth_phy_binds_nodes(struct udevice * + int eth_phy_set_mdio_bus(struct udevice *eth_dev, struct mii_dev *mdio_bus); + struct mii_dev *eth_phy_get_mdio_bus(struct udevice *eth_dev); + int eth_phy_get_addr(struct udevice *dev); ++void eth_phy_reset(struct udevice *dev, int value); + + #endif diff --git a/patch/u-boot/u-boot-filogic/170-cmd-bootmenu-permit-to-select-bootmenu-entry-with.patch b/patch/u-boot/u-boot-filogic/170-cmd-bootmenu-permit-to-select-bootmenu-entry-with.patch new file mode 100644 index 0000000000..a06682a0b8 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/170-cmd-bootmenu-permit-to-select-bootmenu-entry-with.patch @@ -0,0 +1,261 @@ +From 16fd9af92b7ed93ece62fa8d1bef341455d773cf Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Sat, 24 May 2025 23:23:53 +0200 +Subject: [PATCH v2] cmd: bootmenu: permit to select bootmenu entry with a + shortcut + +Permit to select a bootmenu entry with a key shortcut. This is +especially useful in production or testing scenario to automate flashing +procedure or testing procedure. + +The boot entry are changed to append the shortcut key to it. + +Example: + 1. Run default boot command. + 2. Boot system via TFTP. + 3. Boot production system from NAND. + 4. Boot recovery system from NAND. + 5. Load production system via TFTP then write to NAND. + 6. Load recovery system via TFTP then write to NAND. + 7. Load BL31+U-Boot FIP via TFTP then write to NAND. + 8. Load BL2 preloader via TFTP then write to NAND. + 9. Reboot. + a. Reset all settings to factory defaults. + 0. Exit + +0 is always reserved for Exit to console. +On pressing the keyboard key 2, the bootmenu entry 2 is selected and +executed. + +Up to 34 key shortcut (0 excluded as reserved) are supported from 1-9 +and a-z. +If a shortcut key not present in the bootmenu list is pressed, it is +simply ignored and eventually the autoboot is interrupted. + +Capital A-Z are converted to lower a-z and the related option is +selected. + +Suggested-by: Weijie Gao +Signed-off-by: Christian Marangi +--- +Changes v2: +- Fix spelling mistake +- Fix case with '0' + + cmd/bootmenu.c | 41 ++++++++++++++++++++++++++++++++++++++--- + common/menu.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- + include/cli.h | 2 ++ + include/menu.h | 3 +++ + 4 files changed, 85 insertions(+), 5 deletions(-) + +--- a/cmd/bootmenu.c ++++ b/cmd/bootmenu.c +@@ -114,6 +114,14 @@ static char *bootmenu_choice_entry(void + ++menu->active; + /* no menu key selected, regenerate menu */ + return NULL; ++ case BKEY_SHORTCUT: ++ /* invalid shortcut, regenerate menu */ ++ if (cch->shortcut_key >= menu->count - 1) ++ return NULL; ++ /* shortcut_key value for Exit is is -1 */ ++ menu->active = cch->shortcut_key < 0 ? menu->count - 1 : ++ cch->shortcut_key; ++ fallthrough; + case BKEY_SELECT: + iter = menu->first; + for (i = 0; i < menu->active; ++i) +@@ -161,6 +169,21 @@ static void bootmenu_destroy(struct boot + free(menu); + } + ++static char bootmenu_entry_shortcut_key(int index) ++{ ++ switch (index) { ++ /* 1-9 shortcut key (0 reserved) */ ++ case 0 ... 8: ++ return '1' + index; ++ /* a-z shortcut key */ ++ case 9 ... 34: ++ return 'a' + index - 9; ++ /* We support shortcut for up to 34 options (0 reserved) */ ++ default: ++ return -ENOENT; ++ } ++} ++ + /** + * prepare_bootmenu_entry() - generate the bootmenu_xx entries + * +@@ -184,6 +207,8 @@ static int prepare_bootmenu_entry(struct + struct bootmenu_entry *iter = *current; + + while ((option = bootmenu_getoption(i))) { ++ char shortcut_key; ++ int len; + + /* bootmenu_[num] format is "[title]=[commands]" */ + sep = strchr(option, '='); +@@ -196,12 +221,22 @@ static int prepare_bootmenu_entry(struct + if (!entry) + return -ENOMEM; + +- entry->title = strndup(option, sep - option); ++ /* Add shotcut key option: %c. %s\0 */ ++ len = sep - option + 4; ++ ++ entry->title = malloc(len); + if (!entry->title) { + free(entry); + return -ENOMEM; + } + ++ shortcut_key = bootmenu_entry_shortcut_key(i); ++ /* Use emtpy space if entry doesn't support shortcut key */ ++ snprintf(entry->title, len, "%c%c %s", ++ shortcut_key > 0 ? shortcut_key : ' ', ++ shortcut_key > 0 ? '.' : ' ', ++ option); ++ + entry->command = strdup(sep + 1); + if (!entry->command) { + free(entry->title); +@@ -388,9 +423,9 @@ static struct bootmenu_data *bootmenu_cr + + /* Add Quit entry if exiting bootmenu is disabled */ + if (!IS_ENABLED(CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE)) +- entry->title = strdup("Exit"); ++ entry->title = strdup("0. Exit"); + else +- entry->title = strdup("Quit"); ++ entry->title = strdup("0. Quit"); + + if (!entry->title) { + free(entry); +--- a/common/menu.c ++++ b/common/menu.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -436,6 +437,29 @@ int menu_destroy(struct menu *m) + return 1; + } + ++static int bootmenu_conv_shortcut_key(struct bootmenu_data *menu, int ichar) ++{ ++ int shortcut_key; ++ ++ ichar = tolower(ichar); ++ switch (ichar) { ++ /* a-z for bootmenu entry > 9 */ ++ case 'a' ... 'z': ++ shortcut_key = ichar - 'a' + 9; ++ break; ++ /* 1-9 for bootmenu entry <= 9 */ ++ case '1' ... '9': ++ shortcut_key = ichar - '1'; ++ break; ++ /* Reserve 0 for last option (aka Exit) */ ++ case '0': ++ default: ++ return -1; ++ } ++ ++ return shortcut_key; ++} ++ + enum bootmenu_key bootmenu_autoboot_loop(struct bootmenu_data *menu, + struct cli_ch_state *cch) + { +@@ -443,12 +467,12 @@ enum bootmenu_key bootmenu_autoboot_loop + int i, c; + + while (menu->delay > 0) { ++ int ichar; ++ + if (ansi) + printf(ANSI_CURSOR_POSITION, menu->count + 5, 3); + printf("Hit any key to stop autoboot: %d ", menu->delay); + for (i = 0; i < 100; ++i) { +- int ichar; +- + if (!tstc()) { + schedule(); + mdelay(10); +@@ -470,6 +494,11 @@ enum bootmenu_key bootmenu_autoboot_loop + case 0x3: /* ^C */ + key = BKEY_QUIT; + break; ++ case 'A' ... 'Z': ++ case 'a' ... 'z': ++ case '0' ... '9': ++ key = BKEY_SHORTCUT; ++ break; + default: + key = BKEY_NONE; + break; +@@ -477,6 +506,9 @@ enum bootmenu_key bootmenu_autoboot_loop + break; + } + ++ if (key == BKEY_SHORTCUT) ++ cch->shortcut_key = bootmenu_conv_shortcut_key(menu, ichar); ++ + if (menu->delay < 0) + break; + +@@ -524,6 +556,11 @@ enum bootmenu_key bootmenu_conv_key(int + case ' ': + key = BKEY_SPACE; + break; ++ case 'A' ... 'Z': ++ case 'a' ... 'z': ++ case '0' ... '9': ++ key = BKEY_SHORTCUT; ++ break; + default: + key = BKEY_NONE; + break; +@@ -554,5 +591,8 @@ enum bootmenu_key bootmenu_loop(struct b + + key = bootmenu_conv_key(c); + ++ if (key == BKEY_SHORTCUT) ++ cch->shortcut_key = bootmenu_conv_shortcut_key(menu, c); ++ + return key; + } +--- a/include/cli.h ++++ b/include/cli.h +@@ -17,12 +17,14 @@ + * @esc_save: Escape characters collected so far + * @emit_upto: Next index to emit from esc_save + * @emitting: true if emitting from esc_save ++ * @shortcut_key: Selected shortcut option index + */ + struct cli_ch_state { + int esc_len; + char esc_save[8]; + int emit_upto; + bool emitting; ++ int shortcut_key; + }; + + /** +--- a/include/menu.h ++++ b/include/menu.h +@@ -54,6 +54,9 @@ enum bootmenu_key { + BKEY_QUIT, + BKEY_SAVE, + ++ /* shortcut key to select menu option directly */ ++ BKEY_SHORTCUT, ++ + /* 'extra' keys, which are used by menus but not cedit */ + BKEY_PLUS, + BKEY_MINUS, diff --git a/patch/u-boot/u-boot-filogic/200-cmd-add-imsz-and-imszb.patch b/patch/u-boot/u-boot-filogic/200-cmd-add-imsz-and-imszb.patch new file mode 100644 index 0000000000..27cea2fa4e --- /dev/null +++ b/patch/u-boot/u-boot-filogic/200-cmd-add-imsz-and-imszb.patch @@ -0,0 +1,130 @@ +--- a/cmd/bootm.c ++++ b/cmd/bootm.c +@@ -260,6 +260,67 @@ U_BOOT_CMD( + /* iminfo - print header info for a requested image */ + /*******************************************************************/ + #if defined(CONFIG_CMD_IMI) ++#if defined(CONFIG_FIT) ++#define SECTOR_SHIFT 9 ++static int image_totalsize(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[], short int in_blocks) ++{ ++ ulong addr; ++ void *fit; ++ int bsize, tsize; ++ char buf[16]; ++ ++ if (argc >= 2) ++ addr = simple_strtoul(argv[1], NULL, 16); ++ else ++ addr = image_load_addr; ++ ++ fit = (void *)map_sysmem(addr, 0); ++ tsize = fit_get_totalsize(fit); ++ unmap_sysmem(fit); ++ if (tsize == 0) ++ return 1; ++ ++ bsize = (tsize >> SECTOR_SHIFT) + ((tsize & ((1 << SECTOR_SHIFT) - 1))?1:0); ++ ++ if (!in_blocks) ++ snprintf(buf, sizeof(buf), "%x", tsize); ++ else ++ snprintf(buf, sizeof(buf), "%x", bsize); ++ ++ if (argc >= 3) ++ return env_set(argv[2], buf); ++ else ++ printf("%s\n", buf); ++ ++ return 0; ++} ++ ++static int do_imsz(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ return image_totalsize(cmdtp, flag, argc, argv, 0); ++} ++ ++static int do_imszb(struct cmd_tbl *cmdtp, int flag, int argc, ++ char *const argv[]) ++{ ++ return image_totalsize(cmdtp, flag, argc, argv, 1); ++} ++ ++U_BOOT_CMD( ++ imsz, CONFIG_SYS_MAXARGS, 1, do_imsz, ++ "get image total size (in bytes)", ++ "addr [maxhdrlen] [varname]\n" ++); ++ ++U_BOOT_CMD( ++ imszb, CONFIG_SYS_MAXARGS, 1, do_imszb, ++ "get image total size (in blocks)", ++ "addr [maxhdrlen] [varname]\n" ++); ++ ++#endif + static int do_iminfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) + { +--- a/boot/image-fit.c ++++ b/boot/image-fit.c +@@ -2054,6 +2054,47 @@ static const char *fit_get_image_type_pr + return "unknown"; + } + ++size_t fit_get_totalsize(const void *fit) ++{ ++ int ret, ndepth, noffset, images_noffset; ++ size_t data_size, hdrsize, img_total, max_size = 0; ++ const void *data; ++ ++ ret = fdt_check_header(fit); ++ if (ret) { ++ debug("Wrong FIT format: not a flattened device tree (err=%d)\n", ++ ret); ++ return 0; ++ } ++ ++ hdrsize = fdt_totalsize(fit); ++ ++ /* take care of simple FIT with internal images */ ++ max_size = hdrsize; ++ ++ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH); ++ if (images_noffset < 0) ++ goto out; ++ ++ for (ndepth = 0, ++ noffset = fdt_next_node(fit, images_noffset, &ndepth); ++ (noffset >= 0) && (ndepth > 0); ++ noffset = fdt_next_node(fit, noffset, &ndepth)) { ++ if (ndepth == 1) { ++ ret = fit_image_get_data(fit, noffset, &data, &data_size); ++ if (ret) ++ goto out; ++ ++ img_total = data_size + (data - fit); ++ ++ max_size = (max_size > img_total) ? max_size : img_total; ++ } ++ } ++ ++out: ++ return max_size; ++} ++ + int fit_image_load(struct bootm_headers *images, ulong addr, + const char **fit_unamep, const char **fit_uname_configp, + int arch, int ph_type, int bootstage_id, +--- a/include/image.h ++++ b/include/image.h +@@ -1113,6 +1113,7 @@ int fit_parse_subimage(const char *spec, + ulong *addr, const char **image_name); + + int fit_get_subimage_count(const void *fit, int images_noffset); ++size_t fit_get_totalsize(const void *fit); + void fit_print_contents(const void *fit); + void fit_image_print(const void *fit, int noffset, const char *p); + diff --git a/patch/u-boot/u-boot-filogic/211-cmd-bootmenu-custom-title.patch b/patch/u-boot/u-boot-filogic/211-cmd-bootmenu-custom-title.patch new file mode 100644 index 0000000000..3a66aa298a --- /dev/null +++ b/patch/u-boot/u-boot-filogic/211-cmd-bootmenu-custom-title.patch @@ -0,0 +1,33 @@ +--- a/cmd/bootmenu.c ++++ b/cmd/bootmenu.c +@@ -482,7 +482,11 @@ static void menu_display_statusline(stru + printf(ANSI_CURSOR_POSITION, 1, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, 2, 3); +- puts("*** U-Boot Boot Menu ***"); ++ if (menu->mtitle) ++ puts(menu->mtitle); ++ else ++ puts(" *** U-Boot Boot Menu ***"); ++ + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, 3, 1); + puts(ANSI_CLEAR_LINE); +@@ -573,6 +577,7 @@ static enum bootmenu_ret bootmenu_show(i + return BOOTMENU_RET_FAIL; + } + ++ bootmenu->mtitle = env_get("bootmenu_title"); + for (iter = bootmenu->first; iter; iter = iter->next) { + if (menu_item_add(menu, iter->key, iter) != 1) + goto cleanup; +--- a/include/menu.h ++++ b/include/menu.h +@@ -43,6 +43,7 @@ struct bootmenu_data { + int last_active; /* last active menu entry */ + int count; /* total count of menu entries */ + struct bootmenu_entry *first; /* first menu entry */ ++ char *mtitle; /* custom menu title */ + }; + + /** enum bootmenu_key - keys that can be returned by the bootmenu */ diff --git a/patch/u-boot/u-boot-filogic/220-cmd-env-readmem.patch b/patch/u-boot/u-boot-filogic/220-cmd-env-readmem.patch new file mode 100644 index 0000000000..2b3a15fc59 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/220-cmd-env-readmem.patch @@ -0,0 +1,116 @@ +--- a/cmd/Kconfig ++++ b/cmd/Kconfig +@@ -707,6 +707,12 @@ config CMD_ENV_EXISTS + Check if a variable is defined in the environment for use in + shell scripting. + ++config CMD_ENV_READMEM ++ bool "env readmem" ++ default y ++ help ++ Store memory content into environment variable. ++ + config CMD_ENV_CALLBACK + bool "env callbacks - print callbacks and their associated variables" + help +--- a/cmd/nvedit.c ++++ b/cmd/nvedit.c +@@ -273,6 +273,60 @@ static int do_env_ask(struct cmd_tbl *cm + } + #endif + ++#if defined(CONFIG_CMD_ENV_READMEM) ++int do_env_readmem(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) ++{ ++ char varstr[CONFIG_SYS_CBSIZE]; ++ const void *buf; ++ char *local_args[4]; ++ ulong addr, bytes = 6; ++ int hexdump = 0; ++ ++ /* ++ * Check the syntax: ++ * ++ * readmem [-b] name address [size] ++ */ ++ if (argc < 3) ++ return CMD_RET_USAGE; ++ ++ local_args[0] = argv[0]; ++ ++ if (!strncmp(argv[1], "-b", 3)) ++ hexdump = 1; ++ ++ local_args[1] = argv[hexdump + 1]; ++ local_args[2] = varstr; ++ local_args[3] = NULL; ++ ++ addr = simple_strtoul(argv[hexdump + 2], NULL, 16); ++ ++ if (!hexdump) ++ bytes = simple_strtoul(argv[hexdump + 3], NULL, 16); ++ ++ if (bytes < 1) ++ return 1; ++ ++ if ((hexdump * 3) * bytes >= CONFIG_SYS_CBSIZE) ++ return 1; ++ ++ buf = map_sysmem(addr, bytes); ++ if (!buf) ++ return 1; ++ ++ if (hexdump) { ++ sprintf(varstr, "%pM", buf); ++ } else { ++ memcpy(varstr, buf, bytes); ++ varstr[bytes] = '\0'; ++ } ++ unmap_sysmem(buf); ++ ++ /* Continue calling setenv code */ ++ return env_do_env_set(flag, 3, local_args, H_INTERACTIVE); ++} ++#endif ++ + #if defined(CONFIG_CMD_ENV_CALLBACK) + static int print_static_binding(const char *var_name, const char *callback_name, + void *priv) +@@ -1092,6 +1146,9 @@ static struct cmd_tbl cmd_env_sub[] = { + U_BOOT_CMD_MKENT(load, 1, 0, do_env_load, "", ""), + #endif + U_BOOT_CMD_MKENT(print, CONFIG_SYS_MAXARGS, 1, do_env_print, "", ""), ++#if defined(CONFIG_CMD_ENV_READMEM) ++ U_BOOT_CMD_MKENT(readmem, CONFIG_SYS_MAXARGS, 3, do_env_readmem, "", ""), ++#endif + #if defined(CONFIG_CMD_RUN) + U_BOOT_CMD_MKENT(run, CONFIG_SYS_MAXARGS, 1, do_run, "", ""), + #endif +@@ -1176,6 +1233,9 @@ U_BOOT_LONGHELP(env, + #if defined(CONFIG_CMD_NVEDIT_EFI) + "env print -e [-guid guid] [-n] [name ...] - print UEFI environment\n" + #endif ++#if defined(CONFIG_CMD_ENV_READMEM) ++ "env readmem [-b] name address size - read variable from memory\n" ++#endif + #if defined(CONFIG_CMD_RUN) + "env run var [...] - run commands in an environment variable\n" + #endif +@@ -1284,6 +1344,17 @@ U_BOOT_CMD( + ); + #endif + ++#if defined(CONFIG_CMD_ENV_READMEM) ++U_BOOT_CMD_COMPLETE( ++ readmem, CONFIG_SYS_MAXARGS, 3, do_env_readmem, ++ "get environment variable from memory address", ++ "name [-b] address size\n" ++ " - store memory address to env variable\n" ++ " \"-b\": read binary ethaddr", ++ var_complete ++); ++#endif ++ + #if defined(CONFIG_CMD_RUN) + U_BOOT_CMD_COMPLETE( + run, CONFIG_SYS_MAXARGS, 1, do_run, diff --git a/patch/u-boot/u-boot-filogic/230-cmd-add-pstore-check.patch b/patch/u-boot/u-boot-filogic/230-cmd-add-pstore-check.patch new file mode 100644 index 0000000000..48556937bd --- /dev/null +++ b/patch/u-boot/u-boot-filogic/230-cmd-add-pstore-check.patch @@ -0,0 +1,78 @@ +--- a/cmd/pstore.c ++++ b/cmd/pstore.c +@@ -208,6 +208,58 @@ static int pstore_set(struct cmd_tbl *cm + } + + /** ++ * pstore_check() - Check for pstore records ++ * @cmdtp: Command data struct pointer ++ * @flag: Command flag ++ * @argc: Command-line argument count ++ * @argv: Array of command-line arguments ++ * ++ * Return: 0 if there are records in pstore, 1 otherwise ++ */ ++static int pstore_check(struct cmd_tbl *cmdtp, int flag, int argc, ++ char * const argv[]) ++{ ++ phys_addr_t ptr; ++ char *buffer; ++ u32 size; ++ int header_len = 0; ++ bool compressed; ++ ++ if (pstore_length == 0) { ++ printf("Please set PStore configuration\n"); ++ return CMD_RET_USAGE; ++ } ++ ++ if (buffer_size == 0) ++ pstore_init_buffer_size(); ++ ++ buffer = malloc_cache_aligned(buffer_size); ++ ++ ptr = pstore_addr; ++ phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size ++ - pstore_ftrace_size - pstore_console_size; ++ ++ while (ptr < ptr_end) { ++ size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, ++ pstore_record_size, buffer); ++ ptr += pstore_record_size; ++ ++ if (size == 0) ++ continue; ++ ++ header_len = pstore_read_kmsg_hdr(buffer, &compressed); ++ if (header_len == 0) ++ continue; ++ ++ free(buffer); ++ return 0; ++ } ++ ++ free(buffer); ++ return 1; ++} ++ ++/** + * pstore_print_buffer() - Print buffer + * @type: buffer type + * @buffer: buffer to print +@@ -459,6 +511,7 @@ static int pstore_save(struct cmd_tbl *c + + static struct cmd_tbl cmd_pstore_sub[] = { + U_BOOT_CMD_MKENT(set, 8, 0, pstore_set, "", ""), ++ U_BOOT_CMD_MKENT(check, 1, 0, pstore_check, "", ""), + U_BOOT_CMD_MKENT(display, 3, 0, pstore_display, "", ""), + U_BOOT_CMD_MKENT(save, 4, 0, pstore_save, "", ""), + }; +@@ -566,6 +619,8 @@ U_BOOT_CMD(pstore, 10, 0, do_pstore, + " 'pmsg-size' is the size of the user space logs record.\n" + " 'ecc-size' enables/disables ECC support and specifies ECC buffer size in\n" + " bytes (0 disables it, 1 is a special value, means 16 bytes ECC).\n" ++ "pstore check\n" ++ "- Returns true if there are records in pstore.\n" + "pstore display [record-type] [nb]\n" + "- Display existing records in pstore reserved memory. A 'record-type' can\n" + " be given to only display records of this kind. 'record-type' can be one\n" diff --git a/patch/u-boot/u-boot-filogic/250-fix-mmc-erase-timeout.patch b/patch/u-boot/u-boot-filogic/250-fix-mmc-erase-timeout.patch new file mode 100644 index 0000000000..e03b212a74 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/250-fix-mmc-erase-timeout.patch @@ -0,0 +1,11 @@ +--- a/drivers/mmc/mmc_write.c ++++ b/drivers/mmc/mmc_write.c +@@ -79,7 +79,7 @@ ulong mmc_berase(struct blk_desc *block_ + u32 start_rem, blkcnt_rem, erase_args = 0; + struct mmc *mmc = find_mmc_device(dev_num); + lbaint_t blk = 0, blk_r = 0; +- int timeout_ms = 1000; ++ int timeout_ms = blkcnt; + + if (!mmc) + return -1; diff --git a/patch/u-boot/u-boot-filogic/280-image-fdt-save-name-of-FIT-configuration-in-chosen-node.patch b/patch/u-boot/u-boot-filogic/280-image-fdt-save-name-of-FIT-configuration-in-chosen-node.patch new file mode 100644 index 0000000000..3f180340fc --- /dev/null +++ b/patch/u-boot/u-boot-filogic/280-image-fdt-save-name-of-FIT-configuration-in-chosen-node.patch @@ -0,0 +1,31 @@ +From 5f2d5915f8ea4785bc2b8a26955e176a7898c15b Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Tue, 12 Apr 2022 21:00:43 +0100 +Subject: [PATCH] image-fdt: save name of FIT configuration in '/chosen' node + +It can be useful for the OS (Linux) to know which configuration has +been chosen by U-Boot when launching a FIT image. +Store the name of the FIT configuration node used in a new string +property called 'u-boot,bootconf' in the '/chosen' node in device tree. + +Signed-off-by: Daniel Golle +Reviewed-by: Tom Rini +--- + boot/image-fdt.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/boot/image-fdt.c ++++ b/boot/image-fdt.c +@@ -613,6 +613,12 @@ int image_setup_libfdt(struct bootm_head + images->fit_uname_cfg, + strlen(images->fit_uname_cfg) + 1, 1); + ++ /* Store name of configuration node as u-boot,bootconf in /chosen node */ ++ if (images->fit_uname_cfg) ++ fdt_find_and_setprop(blob, "/chosen", "u-boot,bootconf", ++ images->fit_uname_cfg, ++ strlen(images->fit_uname_cfg) + 1, 1); ++ + /* Update ethernet nodes */ + fdt_fixup_ethernet(blob); + #if IS_ENABLED(CONFIG_CMD_PSTORE) diff --git a/patch/u-boot/u-boot-filogic/305-mt7988-generic-reset-button-ignore-env.patch b/patch/u-boot/u-boot-filogic/305-mt7988-generic-reset-button-ignore-env.patch new file mode 100644 index 0000000000..3f239c984a --- /dev/null +++ b/patch/u-boot/u-boot-filogic/305-mt7988-generic-reset-button-ignore-env.patch @@ -0,0 +1,45 @@ +--- a/board/mediatek/mt7988/mt7988_rfb.c ++++ b/board/mediatek/mt7988/mt7988_rfb.c +@@ -4,7 +4,42 @@ + * Author: Sam Shih + */ + ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifndef CONFIG_RESET_BUTTON_LABEL ++#define CONFIG_RESET_BUTTON_LABEL "reset" ++#endif ++ + int board_init(void) + { + return 0; + } ++ ++int board_late_init(void) ++{ ++ gd->env_valid = 1; //to load environment variable from persistent store ++ struct udevice *dev; ++ ++ gd->env_valid = ENV_VALID; ++ if (!button_get_by_label(CONFIG_RESET_BUTTON_LABEL, &dev)) { ++ puts("reset button found\n"); ++#ifdef CONFIG_RESET_BUTTON_SETTLE_DELAY ++ if (CONFIG_RESET_BUTTON_SETTLE_DELAY > 0) { ++ button_get_state(dev); ++ mdelay(CONFIG_RESET_BUTTON_SETTLE_DELAY); ++ } ++#endif ++ if (button_get_state(dev) == BUTTON_ON) { ++ puts("button pushed, resetting environment\n"); ++ gd->env_valid = ENV_INVALID; ++ } ++ } ++ env_relocate(); ++ return 0; ++} diff --git a/patch/u-boot/u-boot-filogic/310-mt7988-select-rootdisk.patch b/patch/u-boot/u-boot-filogic/310-mt7988-select-rootdisk.patch new file mode 100644 index 0000000000..308108e621 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/310-mt7988-select-rootdisk.patch @@ -0,0 +1,67 @@ +--- a/board/mediatek/mt7988/mt7988_rfb.c ++++ b/board/mediatek/mt7988/mt7988_rfb.c +@@ -10,7 +10,9 @@ + #include + #include + #include ++#include + #include ++#include + + #ifndef CONFIG_RESET_BUTTON_LABEL + #define CONFIG_RESET_BUTTON_LABEL "reset" +@@ -43,3 +45,54 @@ int board_late_init(void) + env_relocate(); + return 0; + } ++ ++#define MT7988_BOOT_NOR 0 ++#define MT7988_BOOT_SPIM_NAND 1 ++#define MT7988_BOOT_EMMC 2 ++#define MT7988_BOOT_SNFI_NAND 3 ++ ++int ft_system_setup(void *blob, struct bd_info *bd) ++{ ++ const u32 *media_handle_p; ++ int chosen, len, ret; ++ const char *media; ++ u32 media_handle; ++ ++ switch ((readl(0x1001f6f0) & 0xc00) >> 10) { ++ case MT7988_BOOT_NOR: ++ media = "rootdisk-nor"; ++ break ++ ;; ++ case MT7988_BOOT_SPIM_NAND: ++ media = "rootdisk-spim-nand"; ++ break ++ ;; ++ case MT7988_BOOT_EMMC: ++ media = "rootdisk-emmc"; ++ break ++ ;; ++ case MT7988_BOOT_SNFI_NAND: ++ media = "rootdisk-sd"; ++ break ++ ;; ++ } ++ ++ chosen = fdt_path_offset(blob, "/chosen"); ++ if (chosen <= 0) ++ return 0; ++ ++ media_handle_p = fdt_getprop(blob, chosen, media, &len); ++ if (media_handle_p <= 0 || len != 4) ++ return 0; ++ ++ media_handle = *media_handle_p; ++ ret = fdt_setprop(blob, chosen, "rootdisk", &media_handle, sizeof(media_handle)); ++ if (ret) { ++ printf("cannot set media phandle %s as rootdisk /chosen node\n", media); ++ return ret; ++ } ++ ++ printf("set /chosen/rootdisk to bootrom media: %s (phandle 0x%08x)\n", media, fdt32_to_cpu(media_handle)); ++ ++ return 0; ++} diff --git a/patch/u-boot/u-boot-filogic/341-mtd-spinand-Support-dosilicon.patch b/patch/u-boot/u-boot-filogic/341-mtd-spinand-Support-dosilicon.patch new file mode 100644 index 0000000000..2d62e83343 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/341-mtd-spinand-Support-dosilicon.patch @@ -0,0 +1,329 @@ +From cead43cc5781492f2706eeed1157c8986a216bc9 Mon Sep 17 00:00:00 2001 +From: Jon Lin +Date: Sun, 17 Oct 2021 09:51:39 +0800 +Subject: [PATCH] mtd: spinand: Support dosilicon + +DS35X1GA, DS35Q2GA, DS35M1GA, DS35M2GA, DS35Q2GB, DS35M1GB + +Change-Id: I5aeb0219f01dbe98d36b398e66b94ab31b07788e +Signed-off-by: Jon Lin +--- + drivers/mtd/nand/spi/Makefile | 2 +- + drivers/mtd/nand/spi/core.c | 1 + + drivers/mtd/nand/spi/dosilicon.c | 187 +++++++++++++++++++++++++++++++ + include/linux/mtd/spinand.h | 1 + + 4 files changed, 190 insertions(+), 1 deletion(-) + create mode 100644 drivers/mtd/nand/spi/dosilicon.c + +--- a/drivers/mtd/nand/spi/Makefile ++++ b/drivers/mtd/nand/spi/Makefile +@@ -1,5 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0 + + spinand-objs := core.o esmt.o foresee.o etron.o gigadevice.o macronix.o micron.o paragon.o ++spinand-objs += dosilicon.o + spinand-objs += toshiba.o winbond.o xtx.o + obj-$(CONFIG_MTD_SPI_NAND) += spinand.o +--- a/drivers/mtd/nand/spi/core.c ++++ b/drivers/mtd/nand/spi/core.c +@@ -826,6 +826,7 @@ static const struct nand_ops spinand_ops + }; + + static const struct spinand_manufacturer *spinand_manufacturers[] = { ++ &dosilicon_spinand_manufacturer, + &etron_spinand_manufacturer, + &gigadevice_spinand_manufacturer, + ¯onix_spinand_manufacturer, +--- /dev/null ++++ b/drivers/mtd/nand/spi/dosilicon.c +@@ -0,0 +1,280 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2020 Rockchip Electronics Co., Ltd. ++ */ ++ ++#ifndef __UBOOT__ ++#include ++#include ++#endif ++#include ++ ++#define SPINAND_MFR_DOSILICON 0xE5 ++ ++#define DOSICON_STATUS_ECC_MASK GENMASK(6, 4) ++#define DOSICON_STATUS_ECC_NO_BITFLIPS (0 << 4) ++#define DOSICON_STATUS_ECC_1TO3_BITFLIPS (1 << 4) ++#define DOSICON_STATUS_ECC_4TO6_BITFLIPS (3 << 4) ++#define DOSICON_STATUS_ECC_7TO8_BITFLIPS (5 << 4) ++ ++static SPINAND_OP_VARIANTS(read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(write_cache_variants, ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(update_cache_variants, ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); ++ ++static int ds35xxga_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (16 * section) + 8; ++ region->length = 8; ++ ++ return 0; ++} ++ ++static int ds35xxga_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section > 3) ++ return -ERANGE; ++ ++ region->offset = (16 * section) + 2; ++ region->length = 6; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops ds35xxga_ooblayout = { ++ .ecc = ds35xxga_ooblayout_ecc, ++ .rfree = ds35xxga_ooblayout_free, ++}; ++ ++static int ds35xxgb_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 64; ++ region->length = 64; ++ ++ return 0; ++} ++ ++static int ds35xxgb_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ /* Reserve 1 bytes for the BBM. */ ++ region->offset = 1; ++ region->length = 63; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops ds35xxgb_ooblayout = { ++ .ecc = ds35xxgb_ooblayout_ecc, ++ .rfree = ds35xxgb_ooblayout_free, ++}; ++ ++static int ds35xxgb_ecc_get_status(struct spinand_device *spinand, ++ u8 status) ++{ ++ switch (status & DOSICON_STATUS_ECC_MASK) { ++ case STATUS_ECC_NO_BITFLIPS: ++ return 0; ++ ++ case STATUS_ECC_UNCOR_ERROR: ++ return -EBADMSG; ++ ++ case DOSICON_STATUS_ECC_1TO3_BITFLIPS: ++ return 3; ++ ++ case DOSICON_STATUS_ECC_4TO6_BITFLIPS: ++ return 6; ++ ++ case DOSICON_STATUS_ECC_7TO8_BITFLIPS: ++ return 8; ++ ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++ ++static const struct spinand_info dosilicon_spinand_table[] = { ++ SPINAND_INFO("DS35X1GA", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxga_ooblayout, NULL)), ++ SPINAND_INFO("DS35Q2GA", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x72), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxga_ooblayout, NULL)), ++ SPINAND_INFO("DS35M1GA", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x21), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxga_ooblayout, NULL)), ++ SPINAND_INFO("DS35M2GA", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxga_ooblayout, NULL)), ++ SPINAND_INFO("DS35Q2GB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xF2), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ++ ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35M1GB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA1), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ++ ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q1GB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xF1), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ++ ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q4GM", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xF4), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 2, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ++ ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q12B", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xF5), ++ NAND_MEMORG(1, 2048, 128, 64, 512, 10, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ++ ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35M12B", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA5), ++ NAND_MEMORG(1, 2048, 128, 64, 512, 10, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ++ ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q1GD-IB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x51), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35M4GB-IB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x64), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q4GB-IB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xB4), ++ NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q12C-IB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x75), ++ NAND_MEMORG(1, 2048, 128, 64, 512, 10, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35M12C-IB", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), ++ NAND_MEMORG(1, 2048, 128, 64, 512, 10, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ds35xxgb_ecc_get_status)), ++ SPINAND_INFO("DS35Q2GBS", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xB2), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&ds35xxgb_ooblayout, ds35xxgb_ecc_get_status)), ++}; ++ ++static const struct spinand_manufacturer_ops dosilicon_spinand_manuf_ops = { ++}; ++ ++const struct spinand_manufacturer dosilicon_spinand_manufacturer = { ++ .id = SPINAND_MFR_DOSILICON, ++ .name = "dosilicon", ++ .chips = dosilicon_spinand_table, ++ .nchips = ARRAY_SIZE(dosilicon_spinand_table), ++ .ops = &dosilicon_spinand_manuf_ops, ++}; +--- a/include/linux/mtd/spinand.h ++++ b/include/linux/mtd/spinand.h +@@ -244,6 +244,7 @@ struct spinand_manufacturer { + }; + + /* SPI NAND manufacturers */ ++extern const struct spinand_manufacturer dosilicon_spinand_manufacturer; + extern const struct spinand_manufacturer etron_spinand_manufacturer; + extern const struct spinand_manufacturer gigadevice_spinand_manufacturer; + extern const struct spinand_manufacturer macronix_spinand_manufacturer; diff --git a/patch/u-boot/u-boot-filogic/342-mtd-spinand-Support-fmsh.patch b/patch/u-boot/u-boot-filogic/342-mtd-spinand-Support-fmsh.patch new file mode 100644 index 0000000000..2e23ba4110 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/342-mtd-spinand-Support-fmsh.patch @@ -0,0 +1,290 @@ +From 205a24e34751220c3ba04f0ac6ecc734e56ed225 Mon Sep 17 00:00:00 2001 +From: Jon Lin +Date: Sun, 17 Oct 2021 09:59:10 +0800 +Subject: [PATCH] mtd: spinand: Support fmsh + +FM25S01A, FM25S02A, FM25S01 + +Change-Id: I7e0ceec39c57dc591d77a4ebde599ad326cf25b7 +Signed-off-by: Jon Lin +--- + drivers/mtd/nand/spi/Makefile | 2 +- + drivers/mtd/nand/spi/core.c | 1 + + drivers/mtd/nand/spi/fmsh.c | 122 ++++++++++++++++++++++++++++++++++ + include/linux/mtd/spinand.h | 1 + + 4 files changed, 125 insertions(+), 1 deletion(-) + create mode 100644 drivers/mtd/nand/spi/fmsh.c + +--- a/drivers/mtd/nand/spi/Makefile ++++ b/drivers/mtd/nand/spi/Makefile +@@ -1,6 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0 + + spinand-objs := core.o esmt.o foresee.o etron.o gigadevice.o macronix.o micron.o paragon.o +-spinand-objs += dosilicon.o ++spinand-objs += dosilicon.o fmsh.o + spinand-objs += toshiba.o winbond.o xtx.o + obj-$(CONFIG_MTD_SPI_NAND) += spinand.o +--- a/drivers/mtd/nand/spi/core.c ++++ b/drivers/mtd/nand/spi/core.c +@@ -828,6 +828,7 @@ static const struct nand_ops spinand_ops + static const struct spinand_manufacturer *spinand_manufacturers[] = { + &dosilicon_spinand_manufacturer, + &etron_spinand_manufacturer, ++ &fmsh_spinand_manufacturer, + &gigadevice_spinand_manufacturer, + ¯onix_spinand_manufacturer, + µn_spinand_manufacturer, +--- /dev/null ++++ b/drivers/mtd/nand/spi/fmsh.c +@@ -0,0 +1,240 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd. ++ * ++ * Authors: ++ * Dingqiang Lin ++ */ ++ ++#ifndef __UBOOT__ ++#include ++#include ++#endif ++#include ++ ++#define SPINAND_MFR_FMSH 0xA1 ++ ++static SPINAND_OP_VARIANTS(read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(write_cache_variants, ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(update_cache_variants, ++ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), ++ SPINAND_PROG_LOAD(false, 0, NULL, 0)); ++ ++static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ return -ERANGE; ++} ++ ++static int fm25s01a_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 2; ++ region->length = 62; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops fm25s01a_ooblayout = { ++ .ecc = fm25s01a_ooblayout_ecc, ++ .rfree = fm25s01a_ooblayout_free, ++}; ++ ++static int fm25s01_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 64; ++ region->length = 64; ++ ++ return 0; ++} ++ ++static int fm25s01_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 2; ++ region->length = 62; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops fm25s01_ooblayout = { ++ .ecc = fm25s01_ooblayout_ecc, ++ .rfree = fm25s01_ooblayout_free, ++}; ++ ++/* ++ * ecc bits: 0xC0[4,6] ++ * [0b000], No bit errors were detected; ++ * [0b001] and [0b011], 1~6 Bit errors were detected and corrected. Not ++ * reach Flipping Bits; ++ * [0b101], Bit error count equals the bit flip ++ * detection threshold ++ * [0b010], Multiple bit errors were detected and ++ * not corrected. ++ * others, Reserved. ++ */ ++static int fm25s01bi3_ecc_ecc_get_status(struct spinand_device *spinand, ++ u8 status) ++{ ++ struct nand_device *nand = spinand_to_nand(spinand); ++ u8 eccsr = (status & GENMASK(6, 4)) >> 4; ++ ++ if (eccsr <= 1 || eccsr == 3) ++ return eccsr; ++ else if (eccsr == 5) ++ return nand->eccreq.strength; ++ else ++ return -EBADMSG; ++} ++ ++static int fm25g0xd_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 64; ++ region->length = 64; ++ ++ return 0; ++} ++ ++static int fm25g0xd_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ /* Reserve 2 bytes for the BBM. */ ++ region->offset = 2; ++ region->length = 62; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops fm25g0xd_ooblayout = { ++ .ecc = fm25g0xd_ooblayout_ecc, ++ .rfree = fm25g0xd_ooblayout_free, ++}; ++ ++/* ++ * ecc bits: 0xC0[4,6] ++ * [0x0], No bit errors were detected; ++ * [0x001, 0x011], Bit errors were detected and corrected. Not ++ * reach Flipping Bits; ++ * [0x100], Bit error count equals the bit flip ++ * detectionthreshold ++ * [0x101, 0x110], Reserved; ++ * [0x111], Multiple bit errors were detected and ++ * not corrected. ++ */ ++static int fm25g0xd_ecc_get_status(struct spinand_device *spinand, ++ u8 status) ++{ ++ struct nand_device *nand = spinand_to_nand(spinand); ++ u8 eccsr = (status & GENMASK(6, 4)) >> 4; ++ ++ if (eccsr <= 3) ++ return 0; ++ else if (eccsr == 4) ++ return nand->eccreq.strength; ++ else ++ return -EBADMSG; ++} ++ ++static const struct spinand_info fmsh_spinand_table[] = { ++ SPINAND_INFO("FM25S01A", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(1, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)), ++ SPINAND_INFO("FM25S02A", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE5), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), ++ NAND_ECCREQ(1, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)), ++ SPINAND_INFO("FM25S01", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA1), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(1, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&fm25s01_ooblayout, NULL)), ++ SPINAND_INFO("FM25LS01", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA5), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(1, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&fm25s01_ooblayout, NULL)), ++ SPINAND_INFO("FM25S01BI3", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD4), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&fm25s01_ooblayout, fm25s01bi3_ecc_ecc_get_status)), ++ SPINAND_INFO("FM25S02BI3-DND-A-G3", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD6), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&fm25s01_ooblayout, fm25s01bi3_ecc_ecc_get_status)), ++ SPINAND_INFO("FM25G02D", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xF2), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ SPINAND_HAS_QE_BIT, ++ SPINAND_ECCINFO(&fm25g0xd_ooblayout, fm25g0xd_ecc_get_status)), ++}; ++ ++static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = { ++}; ++ ++const struct spinand_manufacturer fmsh_spinand_manufacturer = { ++ .id = SPINAND_MFR_FMSH, ++ .name = "FMSH", ++ .chips = fmsh_spinand_table, ++ .nchips = ARRAY_SIZE(fmsh_spinand_table), ++ .ops = &fmsh_spinand_manuf_ops, ++}; +--- a/include/linux/mtd/spinand.h ++++ b/include/linux/mtd/spinand.h +@@ -246,6 +246,7 @@ struct spinand_manufacturer { + /* SPI NAND manufacturers */ + extern const struct spinand_manufacturer dosilicon_spinand_manufacturer; + extern const struct spinand_manufacturer etron_spinand_manufacturer; ++extern const struct spinand_manufacturer fmsh_spinand_manufacturer; + extern const struct spinand_manufacturer gigadevice_spinand_manufacturer; + extern const struct spinand_manufacturer macronix_spinand_manufacturer; + extern const struct spinand_manufacturer micron_spinand_manufacturer; diff --git a/patch/u-boot/u-boot-filogic/343-mtd-spinand-gsto-Add-code.patch b/patch/u-boot/u-boot-filogic/343-mtd-spinand-gsto-Add-code.patch new file mode 100644 index 0000000000..deb7396541 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/343-mtd-spinand-gsto-Add-code.patch @@ -0,0 +1,189 @@ +From 1e5200d59e21c8a8fa63badf415becb2301e78a4 Mon Sep 17 00:00:00 2001 +From: Jon Lin +Date: Thu, 27 Apr 2023 22:00:04 +0800 +Subject: [PATCH] mtd: spinand: gsto: Add code + +GSS01GSAK1, GSS02GSAK1 + +Change-Id: I7ee9048d934694803d6d081cb7d0cdc56f114e79 +Signed-off-by: Jon Lin +--- + drivers/mtd/nand/spi/Makefile | 2 +- + drivers/mtd/nand/spi/core.c | 1 + + drivers/mtd/nand/spi/gsto.c | 90 +++++++++++++++++++++++++++++++++++ + include/linux/mtd/spinand.h | 1 + + 4 files changed, 93 insertions(+), 1 deletion(-) + create mode 100644 drivers/mtd/nand/spi/gsto.c + +--- a/drivers/mtd/nand/spi/Makefile ++++ b/drivers/mtd/nand/spi/Makefile +@@ -1,6 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0 + + spinand-objs := core.o esmt.o foresee.o etron.o gigadevice.o macronix.o micron.o paragon.o +-spinand-objs += dosilicon.o fmsh.o ++spinand-objs += dosilicon.o fmsh.o gsto.o + spinand-objs += toshiba.o winbond.o xtx.o + obj-$(CONFIG_MTD_SPI_NAND) += spinand.o +--- a/drivers/mtd/nand/spi/core.c ++++ b/drivers/mtd/nand/spi/core.c +@@ -830,6 +830,7 @@ static const struct spinand_manufacturer + &etron_spinand_manufacturer, + &fmsh_spinand_manufacturer, + &gigadevice_spinand_manufacturer, ++ &gsto_spinand_manufacturer, + ¯onix_spinand_manufacturer, + µn_spinand_manufacturer, + ¶gon_spinand_manufacturer, +--- /dev/null ++++ b/drivers/mtd/nand/spi/gsto.c +@@ -0,0 +1,139 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2023 Rockchip Electronics Co., Ltd. ++ * ++ * Authors: ++ * Dingqiang Lin ++ */ ++ ++#ifndef __UBOOT__ ++#include ++#include ++#endif ++#include ++ ++#define SPINAND_MFR_GSTO 0x52 ++ ++static SPINAND_OP_VARIANTS(read_cache_variants, ++ SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), ++ SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(write_cache_variants, ++ SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), ++ SPINAND_PROG_LOAD(true, 0, NULL, 0)); ++ ++static SPINAND_OP_VARIANTS(update_cache_variants, ++ SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), ++ SPINAND_PROG_LOAD(false, 0, NULL, 0)); ++ ++static int gss0xgsak1_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 32; ++ region->length = 32; ++ ++ return 0; ++} ++ ++static int gss0xgsak1_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 2; ++ region->length = 30; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops gss0xgsak1_ooblayout = { ++ .ecc = gss0xgsak1_ooblayout_ecc, ++ .rfree = gss0xgsak1_ooblayout_free, ++}; ++ ++static int gss0xgsax1_ooblayout_ecc(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 64; ++ region->length = 64; ++ ++ return 0; ++} ++ ++static int gss0xgsax1_ooblayout_free(struct mtd_info *mtd, int section, ++ struct mtd_oob_region *region) ++{ ++ if (section) ++ return -ERANGE; ++ ++ region->offset = 2; ++ region->length = 62; ++ ++ return 0; ++} ++ ++static const struct mtd_ooblayout_ops gss0xgsax1_ooblayout = { ++ .ecc = gss0xgsax1_ooblayout_ecc, ++ .rfree = gss0xgsax1_ooblayout_free, ++}; ++ ++static const struct spinand_info gsto_spinand_table[] = { ++ SPINAND_INFO("GSS01GSAK1", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBA, 0x13), ++ NAND_MEMORG(1, 2048, 64, 64, 1024, 10, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&gss0xgsak1_ooblayout, NULL)), ++ SPINAND_INFO("GSS02GSAK1", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBA, 0x23), ++ NAND_MEMORG(1, 2048, 64, 64, 2048, 20, 1, 1, 1), ++ NAND_ECCREQ(4, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&gss0xgsax1_ooblayout, NULL)), ++ SPINAND_INFO("GSS02GSAX1", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCA, 0x23), ++ NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&gss0xgsax1_ooblayout, NULL)), ++ SPINAND_INFO("GSS01GSAX1", ++ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCA, 0x13), ++ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), ++ NAND_ECCREQ(8, 512), ++ SPINAND_INFO_OP_VARIANTS(&read_cache_variants, ++ &write_cache_variants, ++ &update_cache_variants), ++ 0, ++ SPINAND_ECCINFO(&gss0xgsax1_ooblayout, NULL)), ++}; ++ ++static const struct spinand_manufacturer_ops gsto_spinand_manuf_ops = { ++}; ++ ++const struct spinand_manufacturer gsto_spinand_manufacturer = { ++ .id = SPINAND_MFR_GSTO, ++ .name = "GSTO", ++ .chips = gsto_spinand_table, ++ .nchips = ARRAY_SIZE(gsto_spinand_table), ++ .ops = &gsto_spinand_manuf_ops, ++}; +--- a/include/linux/mtd/spinand.h ++++ b/include/linux/mtd/spinand.h +@@ -248,6 +248,7 @@ extern const struct spinand_manufacturer + extern const struct spinand_manufacturer etron_spinand_manufacturer; + extern const struct spinand_manufacturer fmsh_spinand_manufacturer; + extern const struct spinand_manufacturer gigadevice_spinand_manufacturer; ++extern const struct spinand_manufacturer gsto_spinand_manufacturer; + extern const struct spinand_manufacturer macronix_spinand_manufacturer; + extern const struct spinand_manufacturer micron_spinand_manufacturer; + extern const struct spinand_manufacturer paragon_spinand_manufacturer; diff --git a/patch/u-boot/u-boot-filogic/450-add-bpi-r4.patch b/patch/u-boot/u-boot-filogic/450-add-bpi-r4.patch new file mode 100644 index 0000000000..c930f17b8a --- /dev/null +++ b/patch/u-boot/u-boot-filogic/450-add-bpi-r4.patch @@ -0,0 +1,653 @@ +--- a/configs/mt7988a_bananapi_bpi-r4-emmc_defconfig ++++ b/configs/mt7988a_bananapi_bpi-r4-emmc_defconfig +@@ -0,0 +1,132 @@ ++CONFIG_ARM=y ++CONFIG_SYS_HAS_NONCACHED_MEMORY=y ++CONFIG_POSITION_INDEPENDENT=y ++CONFIG_ARCH_MEDIATEK=y ++CONFIG_TEXT_BASE=0x41e00000 ++CONFIG_SYS_MALLOC_F_LEN=0x4000 ++CONFIG_NR_DRAM_BANKS=1 ++CONFIG_ENV_SIZE=0x40000 ++CONFIG_ENV_OFFSET=0x400000 ++CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4-emmc" ++CONFIG_OF_LIBFDT_OVERLAY=y ++CONFIG_TARGET_MT7988=y ++CONFIG_SYS_LOAD_ADDR=0x50000000 ++CONFIG_PRE_CON_BUF_ADDR=0x4007EF00 ++CONFIG_DEBUG_UART_BASE=0x11000000 ++CONFIG_DEBUG_UART_CLOCK=40000000 ++CONFIG_ENV_OFFSET_REDUND=0x440000 ++CONFIG_PCI=y ++CONFIG_DEBUG_UART=y ++CONFIG_AHCI=y ++CONFIG_FIT=y ++CONFIG_BOOTSTD_BOOTCOMMAND=y ++CONFIG_DISTRO_DEFAULTS=y ++CONFIG_BOOTDELAY=1 ++CONFIG_AUTOBOOT_KEYED=y ++CONFIG_OF_SYSTEM_SETUP=y ++CONFIG_BOOTCOMMAND="bootcmd" ++CONFIG_DEFAULT_FDT_FILE="mediatek/mt7988a-bpi-r4-emmc.dtb" ++CONFIG_SYS_CBSIZE=512 ++CONFIG_SYS_PBSIZE=1049 ++CONFIG_LOGLEVEL=6 ++CONFIG_PRE_CONSOLE_BUFFER=y ++CONFIG_LOG=y ++CONFIG_BOARD_LATE_INIT=y ++CONFIG_SYS_PROMPT="MT7988> " ++CONFIG_CMD_CPU=y ++CONFIG_CMD_UFETCH=y ++CONFIG_CMD_LICENSE=y ++# CONFIG_CMD_BOOTEFI_BOOTMGR is not set ++CONFIG_CMD_BOOTMENU=y ++CONFIG_CMD_ASKENV=y ++CONFIG_CMD_ERASEENV=y ++CONFIG_CMD_ENV_FLAGS=y ++CONFIG_CMD_STRINGS=y ++CONFIG_CMD_DM=y ++CONFIG_CMD_GPIO=y ++CONFIG_CMD_PWM=y ++CONFIG_CMD_GPT=y ++CONFIG_CMD_MMC=y ++CONFIG_CMD_MTD=y ++CONFIG_CMD_PCI=y ++CONFIG_CMD_SF_TEST=y ++CONFIG_CMD_USB=y ++CONFIG_CMD_TFTPSRV=y ++CONFIG_CMD_RARP=y ++CONFIG_CMD_CDP=y ++CONFIG_CMD_SNTP=y ++CONFIG_CMD_LINK_LOCAL=y ++CONFIG_CMD_DNS=y ++# CONFIG_CMD_MII is not set ++CONFIG_CMD_CACHE=y ++CONFIG_CMD_PSTORE=y ++CONFIG_CMD_PSTORE_MEM_ADDR=0x42ff0000 ++CONFIG_CMD_UUID=y ++CONFIG_CMD_HASH=y ++CONFIG_CMD_SMC=y ++CONFIG_CMD_FS_UUID=y ++CONFIG_CMD_UBI=y ++CONFIG_CMD_UBI_RENAME=y ++# CONFIG_ISO_PARTITION is not set ++CONFIG_OF_EMBED=y ++CONFIG_ENV_OVERWRITE=y ++CONFIG_ENV_IS_IN_MMC=y ++CONFIG_SYS_REDUNDAND_ENVIRONMENT=y ++CONFIG_SYS_RELOC_GD_ENV_ADDR=y ++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y ++CONFIG_VERSION_VARIABLE=y ++CONFIG_NETCONSOLE=y ++CONFIG_USE_IPADDR=y ++CONFIG_IPADDR="192.168.1.1" ++CONFIG_USE_SERVERIP=y ++CONFIG_SERVERIP="192.168.1.254" ++CONFIG_NET_RANDOM_ETHADDR=y ++CONFIG_SCSI_AHCI=y ++CONFIG_AHCI_PCI=y ++CONFIG_MTK_AHCI=y ++CONFIG_BUTTON=y ++CONFIG_BUTTON_GPIO=y ++CONFIG_CLK=y ++CONFIG_GPIO_HOG=y ++CONFIG_LED=y ++CONFIG_LED_BLINK=y ++CONFIG_LED_GPIO=y ++CONFIG_SUPPORT_EMMC_BOOT=y ++CONFIG_MMC_HS200_SUPPORT=y ++CONFIG_MMC_MTK=y ++CONFIG_MTD=y ++CONFIG_DM_MTD=y ++CONFIG_MTD_SPI_NAND=y ++CONFIG_DM_SPI_FLASH=y ++CONFIG_SPI_FLASH_WINBOND=y ++# CONFIG_SPI_FLASH_USE_4K_SECTORS is not set ++CONFIG_SPI_FLASH_MTD=y ++CONFIG_MTD_UBI_FASTMAP=y ++CONFIG_PHY_FIXED=y ++CONFIG_MEDIATEK_ETH=y ++CONFIG_PCIE_MEDIATEK=y ++CONFIG_PHY=y ++CONFIG_PHY_MTK_TPHY=y ++CONFIG_PINCTRL=y ++CONFIG_PINCONF=y ++CONFIG_PINCTRL_MT7988=y ++CONFIG_POWER_DOMAIN=y ++CONFIG_MTK_POWER_DOMAIN=y ++CONFIG_DM_REGULATOR=y ++CONFIG_DM_REGULATOR_FIXED=y ++CONFIG_DM_REGULATOR_GPIO=y ++CONFIG_DM_PWM=y ++CONFIG_PWM_MTK=y ++CONFIG_RAM=y ++CONFIG_SCSI=y ++CONFIG_DM_SERIAL=y ++CONFIG_SERIAL_RX_BUFFER=y ++CONFIG_MTK_SERIAL=y ++CONFIG_SPI=y ++CONFIG_DM_SPI=y ++CONFIG_MTK_SPIM=y ++CONFIG_USB=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_XHCI_MTK=y ++CONFIG_ZSTD=y ++CONFIG_HEXDUMP=y +--- a/configs/mt7988a_bananapi_bpi-r4-sdmmc_defconfig ++++ b/configs/mt7988a_bananapi_bpi-r4-sdmmc_defconfig +@@ -0,0 +1,132 @@ ++CONFIG_ARM=y ++CONFIG_SYS_HAS_NONCACHED_MEMORY=y ++CONFIG_POSITION_INDEPENDENT=y ++CONFIG_ARCH_MEDIATEK=y ++CONFIG_TEXT_BASE=0x41e00000 ++CONFIG_SYS_MALLOC_F_LEN=0x4000 ++CONFIG_NR_DRAM_BANKS=1 ++CONFIG_ENV_SIZE=0x40000 ++CONFIG_ENV_OFFSET=0x400000 ++CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4-sd" ++CONFIG_OF_LIBFDT_OVERLAY=y ++CONFIG_TARGET_MT7988=y ++CONFIG_SYS_LOAD_ADDR=0x50000000 ++CONFIG_PRE_CON_BUF_ADDR=0x4007EF00 ++CONFIG_DEBUG_UART_BASE=0x11000000 ++CONFIG_DEBUG_UART_CLOCK=40000000 ++CONFIG_ENV_OFFSET_REDUND=0x440000 ++CONFIG_PCI=y ++CONFIG_DEBUG_UART=y ++CONFIG_AHCI=y ++CONFIG_FIT=y ++CONFIG_BOOTSTD_BOOTCOMMAND=y ++CONFIG_DISTRO_DEFAULTS=y ++CONFIG_BOOTDELAY=1 ++CONFIG_AUTOBOOT_KEYED=y ++CONFIG_OF_SYSTEM_SETUP=y ++CONFIG_BOOTCOMMAND="bootcmd" ++CONFIG_DEFAULT_FDT_FILE="mediatek/mt7988a-bpi-r4-sd.dtb" ++CONFIG_SYS_CBSIZE=512 ++CONFIG_SYS_PBSIZE=1049 ++CONFIG_LOGLEVEL=6 ++CONFIG_PRE_CONSOLE_BUFFER=y ++CONFIG_LOG=y ++CONFIG_BOARD_LATE_INIT=y ++CONFIG_SYS_PROMPT="MT7988> " ++CONFIG_CMD_CPU=y ++CONFIG_CMD_UFETCH=y ++CONFIG_CMD_LICENSE=y ++# CONFIG_CMD_BOOTEFI_BOOTMGR is not set ++CONFIG_CMD_BOOTMENU=y ++CONFIG_CMD_ASKENV=y ++CONFIG_CMD_ERASEENV=y ++CONFIG_CMD_ENV_FLAGS=y ++CONFIG_CMD_STRINGS=y ++CONFIG_CMD_DM=y ++CONFIG_CMD_GPIO=y ++CONFIG_CMD_PWM=y ++CONFIG_CMD_GPT=y ++CONFIG_CMD_MMC=y ++CONFIG_CMD_MTD=y ++CONFIG_CMD_PCI=y ++CONFIG_CMD_SF_TEST=y ++CONFIG_CMD_USB=y ++CONFIG_CMD_TFTPSRV=y ++CONFIG_CMD_RARP=y ++CONFIG_CMD_CDP=y ++CONFIG_CMD_SNTP=y ++CONFIG_CMD_LINK_LOCAL=y ++CONFIG_CMD_DNS=y ++# CONFIG_CMD_MII is not set ++CONFIG_CMD_CACHE=y ++CONFIG_CMD_PSTORE=y ++CONFIG_CMD_PSTORE_MEM_ADDR=0x42ff0000 ++CONFIG_CMD_UUID=y ++CONFIG_CMD_HASH=y ++CONFIG_CMD_SMC=y ++CONFIG_CMD_FS_UUID=y ++CONFIG_CMD_UBI=y ++CONFIG_CMD_UBI_RENAME=y ++# CONFIG_ISO_PARTITION is not set ++CONFIG_OF_EMBED=y ++CONFIG_ENV_OVERWRITE=y ++CONFIG_ENV_IS_IN_MMC=y ++CONFIG_SYS_REDUNDAND_ENVIRONMENT=y ++CONFIG_SYS_RELOC_GD_ENV_ADDR=y ++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y ++CONFIG_VERSION_VARIABLE=y ++CONFIG_NETCONSOLE=y ++CONFIG_USE_IPADDR=y ++CONFIG_IPADDR="192.168.1.1" ++CONFIG_USE_SERVERIP=y ++CONFIG_SERVERIP="192.168.1.254" ++CONFIG_NET_RANDOM_ETHADDR=y ++CONFIG_SCSI_AHCI=y ++CONFIG_AHCI_PCI=y ++CONFIG_MTK_AHCI=y ++CONFIG_BUTTON=y ++CONFIG_BUTTON_GPIO=y ++CONFIG_CLK=y ++CONFIG_GPIO_HOG=y ++CONFIG_LED=y ++CONFIG_LED_BLINK=y ++CONFIG_LED_GPIO=y ++CONFIG_SUPPORT_EMMC_BOOT=y ++CONFIG_MMC_HS200_SUPPORT=y ++CONFIG_MMC_MTK=y ++CONFIG_MTD=y ++CONFIG_DM_MTD=y ++CONFIG_MTD_SPI_NAND=y ++CONFIG_DM_SPI_FLASH=y ++CONFIG_SPI_FLASH_WINBOND=y ++# CONFIG_SPI_FLASH_USE_4K_SECTORS is not set ++CONFIG_SPI_FLASH_MTD=y ++CONFIG_MTD_UBI_FASTMAP=y ++CONFIG_PHY_FIXED=y ++CONFIG_MEDIATEK_ETH=y ++CONFIG_PCIE_MEDIATEK=y ++CONFIG_PHY=y ++CONFIG_PHY_MTK_TPHY=y ++CONFIG_PINCTRL=y ++CONFIG_PINCONF=y ++CONFIG_PINCTRL_MT7988=y ++CONFIG_POWER_DOMAIN=y ++CONFIG_MTK_POWER_DOMAIN=y ++CONFIG_DM_REGULATOR=y ++CONFIG_DM_REGULATOR_FIXED=y ++CONFIG_DM_REGULATOR_GPIO=y ++CONFIG_DM_PWM=y ++CONFIG_PWM_MTK=y ++CONFIG_RAM=y ++CONFIG_SCSI=y ++CONFIG_DM_SERIAL=y ++CONFIG_SERIAL_RX_BUFFER=y ++CONFIG_MTK_SERIAL=y ++CONFIG_SPI=y ++CONFIG_DM_SPI=y ++CONFIG_MTK_SPIM=y ++CONFIG_USB=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_XHCI_MTK=y ++CONFIG_ZSTD=y ++CONFIG_HEXDUMP=y +--- a/configs/mt7988a_bananapi_bpi-r4-snand_defconfig ++++ b/configs/mt7988a_bananapi_bpi-r4-snand_defconfig +@@ -0,0 +1,132 @@ ++CONFIG_ARM=y ++CONFIG_SYS_HAS_NONCACHED_MEMORY=y ++CONFIG_POSITION_INDEPENDENT=y ++CONFIG_ARCH_MEDIATEK=y ++CONFIG_TEXT_BASE=0x41e00000 ++CONFIG_SYS_MALLOC_F_LEN=0x4000 ++CONFIG_NR_DRAM_BANKS=1 ++CONFIG_ENV_SIZE=0x40000 ++CONFIG_ENV_OFFSET=0x400000 ++CONFIG_DEFAULT_DEVICE_TREE="mt7988a-bananapi-bpi-r4-emmc" ++CONFIG_OF_LIBFDT_OVERLAY=y ++CONFIG_TARGET_MT7988=y ++CONFIG_SYS_LOAD_ADDR=0x50000000 ++CONFIG_PRE_CON_BUF_ADDR=0x4007EF00 ++CONFIG_DEBUG_UART_BASE=0x11000000 ++CONFIG_DEBUG_UART_CLOCK=40000000 ++CONFIG_ENV_OFFSET_REDUND=0x440000 ++CONFIG_PCI=y ++CONFIG_DEBUG_UART=y ++CONFIG_AHCI=y ++CONFIG_FIT=y ++CONFIG_BOOTSTD_BOOTCOMMAND=y ++CONFIG_DISTRO_DEFAULTS=y ++CONFIG_BOOTDELAY=1 ++CONFIG_AUTOBOOT_KEYED=y ++CONFIG_OF_SYSTEM_SETUP=y ++CONFIG_BOOTCOMMAND="bootcmd" ++CONFIG_DEFAULT_FDT_FILE="mediatek/mt7988a-bpi-r4-emmc.dtb" ++CONFIG_SYS_CBSIZE=512 ++CONFIG_SYS_PBSIZE=1049 ++CONFIG_LOGLEVEL=6 ++CONFIG_PRE_CONSOLE_BUFFER=y ++CONFIG_LOG=y ++CONFIG_BOARD_LATE_INIT=y ++CONFIG_SYS_PROMPT="MT7988> " ++CONFIG_CMD_CPU=y ++CONFIG_CMD_UFETCH=y ++CONFIG_CMD_LICENSE=y ++# CONFIG_CMD_BOOTEFI_BOOTMGR is not set ++CONFIG_CMD_BOOTMENU=y ++CONFIG_CMD_ASKENV=y ++CONFIG_CMD_ERASEENV=y ++CONFIG_CMD_ENV_FLAGS=y ++CONFIG_CMD_STRINGS=y ++CONFIG_CMD_DM=y ++CONFIG_CMD_GPIO=y ++CONFIG_CMD_PWM=y ++CONFIG_CMD_GPT=y ++CONFIG_CMD_MMC=y ++CONFIG_CMD_MTD=y ++CONFIG_CMD_PCI=y ++CONFIG_CMD_SF_TEST=y ++CONFIG_CMD_USB=y ++CONFIG_CMD_TFTPSRV=y ++CONFIG_CMD_RARP=y ++CONFIG_CMD_CDP=y ++CONFIG_CMD_SNTP=y ++CONFIG_CMD_LINK_LOCAL=y ++CONFIG_CMD_DNS=y ++# CONFIG_CMD_MII is not set ++CONFIG_CMD_CACHE=y ++CONFIG_CMD_PSTORE=y ++CONFIG_CMD_PSTORE_MEM_ADDR=0x42ff0000 ++CONFIG_CMD_UUID=y ++CONFIG_CMD_HASH=y ++CONFIG_CMD_SMC=y ++CONFIG_CMD_FS_UUID=y ++CONFIG_CMD_UBI=y ++CONFIG_CMD_UBI_RENAME=y ++# CONFIG_ISO_PARTITION is not set ++CONFIG_OF_EMBED=y ++CONFIG_ENV_OVERWRITE=y ++CONFIG_ENV_IS_IN_MMC=y ++CONFIG_SYS_REDUNDAND_ENVIRONMENT=y ++CONFIG_SYS_RELOC_GD_ENV_ADDR=y ++CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y ++CONFIG_VERSION_VARIABLE=y ++CONFIG_NETCONSOLE=y ++CONFIG_USE_IPADDR=y ++CONFIG_IPADDR="192.168.1.1" ++CONFIG_USE_SERVERIP=y ++CONFIG_SERVERIP="192.168.1.254" ++CONFIG_NET_RANDOM_ETHADDR=y ++CONFIG_SCSI_AHCI=y ++CONFIG_AHCI_PCI=y ++CONFIG_MTK_AHCI=y ++CONFIG_BUTTON=y ++CONFIG_BUTTON_GPIO=y ++CONFIG_CLK=y ++CONFIG_GPIO_HOG=y ++CONFIG_LED=y ++CONFIG_LED_BLINK=y ++CONFIG_LED_GPIO=y ++CONFIG_SUPPORT_EMMC_BOOT=y ++CONFIG_MMC_HS200_SUPPORT=y ++CONFIG_MMC_MTK=y ++CONFIG_MTD=y ++CONFIG_DM_MTD=y ++CONFIG_MTD_SPI_NAND=y ++CONFIG_DM_SPI_FLASH=y ++CONFIG_SPI_FLASH_WINBOND=y ++# CONFIG_SPI_FLASH_USE_4K_SECTORS is not set ++CONFIG_SPI_FLASH_MTD=y ++CONFIG_MTD_UBI_FASTMAP=y ++CONFIG_PHY_FIXED=y ++CONFIG_MEDIATEK_ETH=y ++CONFIG_PCIE_MEDIATEK=y ++CONFIG_PHY=y ++CONFIG_PHY_MTK_TPHY=y ++CONFIG_PINCTRL=y ++CONFIG_PINCONF=y ++CONFIG_PINCTRL_MT7988=y ++CONFIG_POWER_DOMAIN=y ++CONFIG_MTK_POWER_DOMAIN=y ++CONFIG_DM_REGULATOR=y ++CONFIG_DM_REGULATOR_FIXED=y ++CONFIG_DM_REGULATOR_GPIO=y ++CONFIG_DM_PWM=y ++CONFIG_PWM_MTK=y ++CONFIG_RAM=y ++CONFIG_SCSI=y ++CONFIG_DM_SERIAL=y ++CONFIG_SERIAL_RX_BUFFER=y ++CONFIG_MTK_SERIAL=y ++CONFIG_SPI=y ++CONFIG_DM_SPI=y ++CONFIG_MTK_SPIM=y ++CONFIG_USB=y ++CONFIG_USB_XHCI_HCD=y ++CONFIG_USB_XHCI_MTK=y ++CONFIG_ZSTD=y ++CONFIG_HEXDUMP=y +--- a/arch/arm/dts/mt7988a-bananapi-bpi-r4.dtsi ++++ b/arch/arm/dts/mt7988a-bananapi-bpi-r4.dtsi +@@ -0,0 +1,199 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2022 MediaTek Inc. ++ * Author: Sam Shih ++ */ ++ ++/dts-v1/; ++#include "mt7988.dtsi" ++#include ++#include ++ ++/ { ++ model = "Bananapi BPI-R4"; ++ compatible = "bananapi,bpi-r4", "mediatek,mt7988"; ++ ++ chosen { ++ stdout-path = &uart0; ++ }; ++ ++ memory@40000000 { ++ device_type = "memory"; ++ reg = <0 0x40000000 0 0x10000000>; ++ }; ++ ++ reg_3p3v: regulator-3p3v { ++ compatible = "regulator-fixed"; ++ regulator-name = "fixed-3.3V"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-boot-on; ++ regulator-always-on; ++ }; ++ ++ reg_1p8v: regulator-1p8v { ++ compatible = "regulator-fixed"; ++ regulator-name = "fixed-1.8V"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <1800000>; ++ regulator-boot-on; ++ regulator-always-on; ++ }; ++ ++ keys { ++ compatible = "gpio-keys"; ++ ++ wps { ++ label = "reset"; ++ linux,code = ; ++ gpios = <&pio 14 GPIO_ACTIVE_LOW>; ++ }; ++ }; ++ ++ leds { ++ compatible = "gpio-leds"; ++ ++ led_status_green: led-green { ++ label = "green:status"; ++ gpios = <&pio 79 GPIO_ACTIVE_HIGH>; ++ }; ++ ++ led_status_blue: led-blue { ++ label = "blue:status"; ++ gpios = <&pio 63 GPIO_ACTIVE_HIGH>; ++ }; ++ }; ++}; ++ ++&uart0 { ++ status = "okay"; ++}; ++ ++&i2c1 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&i2c1_pins>; ++ status = "okay"; ++}; ++ ++ð0 { ++ status = "okay"; ++ mediatek,gmac-id = <0>; ++ phy-mode = "usxgmii"; ++ mediatek,switch = "mt7988"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ pause; ++ }; ++}; ++ ++&pio { ++ i2c1_pins: i2c1-pins { ++ mux { ++ function = "i2c"; ++ groups = "i2c1_0"; ++ }; ++ }; ++ ++ pwm_pins: pwm-pins { ++ mux { ++ function = "pwm"; ++ groups = "pwm0", "pwm1", "pwm2", "pwm3", "pwm4", ++ "pwm5", "pwm6", "pwm7"; ++ }; ++ }; ++ ++ spi0_pins: spi0-pins { ++ mux { ++ function = "spi"; ++ groups = "spi0", "spi0_wp_hold"; ++ }; ++ }; ++ ++ mmc0_pins_default: mmc0default { ++ mux { ++ function = "flash"; ++ groups = "emmc_51"; ++ }; ++ ++ conf-cmd-dat { ++ pins = "EMMC_DATA_0", "EMMC_DATA_1", "EMMC_DATA_2", ++ "EMMC_DATA_3", "EMMC_DATA_4", "EMMC_DATA_5", ++ "EMMC_DATA_6", "EMMC_DATA_7", "EMMC_CMD"; ++ input-enable; ++ }; ++ ++ conf-clk { ++ pins = "EMMC_CK"; ++ }; ++ ++ conf-dsl { ++ pins = "EMMC_DSL"; ++ }; ++ ++ conf-rst { ++ pins = "EMMC_RSTB"; ++ }; ++ }; ++ ++ mmc1_pins_default: mmc1default { ++ mux { ++ function = "flash"; ++ groups = "emmc_45"; ++ }; ++ ++ conf-cmd-dat { ++ pins = "SPI2_CSB", "SPI2_MISO", "SPI2_MOSI", ++ "SPI2_CLK", "SPI2_HOLD"; ++ input-enable; ++ }; ++ ++ conf-clk { ++ pins = "SPI2_WP"; ++ }; ++ }; ++}; ++ ++&pwm { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&pwm_pins>; ++ status = "okay"; ++}; ++ ++&spi0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&spi0_pins>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ status = "okay"; ++ must_tx; ++ enhance_timing; ++ dma_ext; ++ ipm_design; ++ support_quad; ++ tick_dly = <2>; ++ sample_sel = <0>; ++ ++ spi_nand@0 { ++ compatible = "spi-nand"; ++ reg = <0>; ++ spi-max-frequency = <52000000>; ++ partitions { ++ compatible = "fixed-partitions"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ partition@0 { ++ label = "bl2"; ++ reg = <0x0 0x200000>; ++ }; ++ ++ partition@200000 { ++ label = "ubi"; ++ reg = <0x200000 0x7e00000>; ++ compatible = "linux,ubi"; ++ }; ++ }; ++ }; ++}; +--- a/arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts ++++ b/arch/arm/dts/mt7988a-bananapi-bpi-r4-sd.dts +@@ -0,0 +1,19 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2022 MediaTek Inc. ++ * Author: Sam Shih ++ */ ++ ++/dts-v1/; ++#include "mt7988a-bananapi-bpi-r4.dtsi" ++ ++&mmc0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mmc1_pins_default>; ++ max-frequency = <52000000>; ++ bus-width = <4>; ++ cap-sd-highspeed; ++ vmmc-supply = <®_3p3v>; ++ vqmmc-supply = <®_3p3v>; ++ status = "okay"; ++}; +--- a/arch/arm/dts/mt7988a-bananapi-bpi-r4-emmc.dts ++++ b/arch/arm/dts/mt7988a-bananapi-bpi-r4-emmc.dts +@@ -0,0 +1,21 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2022 MediaTek Inc. ++ * Author: Sam Shih ++ */ ++ ++/dts-v1/; ++#include "mt7988a-bananapi-bpi-r4.dtsi" ++ ++&mmc0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mmc0_pins_default>; ++ max-frequency = <52000000>; ++ bus-width = <8>; ++ cap-mmc-highspeed; ++ cap-mmc-hw-reset; ++ vmmc-supply = <®_3p3v>; ++ vqmmc-supply = <®_1p8v>; ++ non-removable; ++ status = "okay"; ++}; diff --git a/patch/u-boot/u-boot-filogic/500-add-distro-boot-support.patch b/patch/u-boot/u-boot-filogic/500-add-distro-boot-support.patch new file mode 100644 index 0000000000..5b13f30ed3 --- /dev/null +++ b/patch/u-boot/u-boot-filogic/500-add-distro-boot-support.patch @@ -0,0 +1,29 @@ +--- a/include/configs/mt7988.h ++++ b/include/configs/mt7988.h +@@ -11,4 +11,26 @@ + + #define CFG_MAX_MEM_MAPPED 0xC0000000 + ++#include ++ ++#ifndef BOOT_TARGETS ++#define BOOT_TARGETS "mmc0 nvme usb pxe dhcp spi" + #endif ++ ++#define ENV_MEM_LAYOUT_SETTINGS \ ++ "scriptaddr=0x50000000\0" \ ++ "script_size_f=0x2000\0" \ ++ "kernel_addr_r=0x52000000\0" \ ++ "fdt_addr_r=0x62000000\0" \ ++ "ramdisk_addr_r=0x62180000\0" ++ ++#include ++ ++#define CFG_EXTRA_ENV_SETTINGS \ ++ "fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0" \ ++ ENV_MEM_LAYOUT_SETTINGS \ ++ "boot_targets=" BOOT_TARGETS "\0" \ ++ "bootcmd=sysboot mmc 0:5 any ${scriptaddr} /boot/extlinux/extlinux.conf\0" \ ++ "\0" ++ ++#endif /* _MT7988_H_ */