--- /dev/null
+CONFIG_ARCH_32BIT_OFF_T=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MMAP_RND_BITS_MAX=15
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=15
+CONFIG_BLK_MQ_PCI=y
+CONFIG_CLKSRC_MMIO=y
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+CONFIG_COMMON_CLK_EN7523=y
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_CPU_BIG_ENDIAN=y
+CONFIG_CPU_GENERIC_DUMP_TLB=y
+CONFIG_CPU_HAS_DIEI=y
+CONFIG_CPU_HAS_PREFETCH=y
+CONFIG_CPU_HAS_RIXI=y
+CONFIG_CPU_HAS_SYNC=y
+CONFIG_CPU_MIPS32=y
+# CONFIG_CPU_MIPS32_R1 is not set
+CONFIG_CPU_MIPS32_R2=y
+CONFIG_CPU_MIPSR2=y
+CONFIG_CPU_MITIGATIONS=y
+CONFIG_CPU_NEEDS_NO_SMARTMIPS_OR_MICROMIPS=y
+CONFIG_CPU_R4K_CACHE_TLB=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_SUPPORTS_32BIT_KERNEL=y
+CONFIG_CPU_SUPPORTS_HIGHMEM=y
+CONFIG_CPU_SUPPORTS_MSA=y
+CONFIG_CRC16=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_HASH_INFO=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_GF128MUL=y
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=2
+CONFIG_CRYPTO_LIB_UTILS=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_ZBOOT=y
+CONFIG_DMA_NEED_SYNC=y
+CONFIG_DMA_NONCOHERENT=y
+CONFIG_DTB_ECONET_NONE=y
+# CONFIG_DTB_ECONET_SMARTFIBER_XP8421_B is not set
+CONFIG_DTC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_EARLY_PRINTK_8250=y
+CONFIG_ECONET=y
+CONFIG_ECONET_EN751221_INTC=y
+CONFIG_ECONET_EN751221_TIMER=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_FS_IOMAP=y
+CONFIG_FUNCTION_ALIGNMENT=0
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GENERIC_ATOMIC64=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IOMAP=y
+CONFIG_GENERIC_IRQ_CHIP=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_LIB_ASHLDI3=y
+CONFIG_GENERIC_LIB_ASHRDI3=y
+CONFIG_GENERIC_LIB_CMPDI2=y
+CONFIG_GENERIC_LIB_LSHRDI3=y
+CONFIG_GENERIC_LIB_UCMPDI2=y
+CONFIG_GENERIC_MSI_IRQ=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_PHY=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GPIO_CDEV=y
+# CONFIG_GPIO_EN7523 is not set
+CONFIG_HARDWARE_WATCHPOINTS=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_MIPS_CPU=y
+CONFIG_IRQ_WORK=y
+CONFIG_JFFS2_FS=y
+CONFIG_JFFS2_ZLIB=y
+CONFIG_LIBFDT=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MIGRATION=y
+CONFIG_MIPS=y
+CONFIG_MIPS_ASID_BITS=8
+CONFIG_MIPS_ASID_SHIFT=0
+# CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER is not set
+CONFIG_MIPS_CMDLINE_FROM_DTB=y
+CONFIG_MIPS_L1_CACHE_SHIFT=5
+# CONFIG_MIPS_NO_APPENDED_DTB is not set
+CONFIG_MIPS_NR_CPU_NR_MAP=2
+CONFIG_MIPS_RAW_APPENDED_DTB=y
+CONFIG_MIPS_SPRAM=y
+CONFIG_MMU_LAZY_TLB_REFCOUNT=y
+CONFIG_MODULES_USE_ELF_REL=y
+CONFIG_MTD_NAND_CORE=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_MTK_BMT=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=13
+CONFIG_MTD_UBI_BLOCK=y
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEED_SRCU_NMI_SAFE=y
+CONFIG_NET_EGRESS=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_INGRESS=y
+CONFIG_NET_XGRESS=y
+CONFIG_NR_CPUS=2
+CONFIG_NVMEM=y
+CONFIG_NVMEM_LAYOUTS=y
+CONFIG_NVMEM_SYSFS=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+CONFIG_PCI=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIE_MEDIATEK=y
+CONFIG_PCI_DISABLE_COMMON_QUIRKS=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DOMAINS_GENERIC=y
+CONFIG_PCI_DRIVERS_GENERIC=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_MSI_ARCH_FALLBACKS=y
+CONFIG_PERF_USE_VMALLOC=y
+CONFIG_PGTABLE_LEVELS=2
+CONFIG_PHY_EN7528_PCIE=y
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RAS=y
+CONFIG_RATIONAL=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_MMIO=y
+CONFIG_RESET_CONTROLLER=y
+CONFIG_RFS_ACCEL=y
+CONFIG_RPS=y
+# CONFIG_SERIAL_8250_AIROHA is not set
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SGL_ALLOC=y
+CONFIG_SMP=y
+CONFIG_SMP_UP=y
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+CONFIG_SOC_ECONET_EN751221=y
+# CONFIG_SOC_ECONET_EN7528 is not set
+CONFIG_SPI=y
+CONFIG_SPI_AIROHA_EN7523=y
+# CONFIG_SPI_AIROHA_SNFI is not set
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_SYS_HAS_CPU_MIPS32_R1=y
+CONFIG_SYS_HAS_CPU_MIPS32_R2=y
+CONFIG_SYS_HAS_EARLY_PRINTK=y
+CONFIG_SYS_SUPPORTS_32BIT_KERNEL=y
+CONFIG_SYS_SUPPORTS_ARBIT_HZ=y
+CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
+CONFIG_SYS_SUPPORTS_LITTLE_ENDIAN=y
+CONFIG_SYS_SUPPORTS_MIPS16=y
+CONFIG_SYS_SUPPORTS_SMP=y
+CONFIG_SYS_SUPPORTS_ZBOOT=y
+CONFIG_SYS_SUPPORTS_ZBOOT_UART16550=y
+CONFIG_TARGET_ISA_REV=2
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UBIFS_FS=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USE_GENERIC_EARLY_PRINTK_8250=y
+CONFIG_USE_OF=y
+CONFIG_XPS=y
+CONFIG_XXHASH=y
+CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZSTD_COMMON=y
+CONFIG_ZSTD_COMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
--- /dev/null
+CONFIG_ARCH_32BIT_OFF_T=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MMAP_RND_BITS_MAX=15
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=15
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_BLK_MQ_PCI=y
+CONFIG_BOARD_SCACHE=y
+CONFIG_CLKSRC_MMIO=y
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+CONFIG_COMMON_CLK_EN7523=y
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_CPU_BIG_ENDIAN=y
+CONFIG_CPU_GENERIC_DUMP_TLB=y
+CONFIG_CPU_HAS_DIEI=y
+CONFIG_CPU_HAS_PREFETCH=y
+CONFIG_CPU_HAS_RIXI=y
+CONFIG_CPU_HAS_SYNC=y
+CONFIG_CPU_MIPS32=y
+# CONFIG_CPU_MIPS32_R1 is not set
+CONFIG_CPU_MIPS32_R2=y
+CONFIG_CPU_MIPSR2=y
+CONFIG_CPU_MIPSR2_IRQ_EI=y
+CONFIG_CPU_MIPSR2_IRQ_VI=y
+CONFIG_CPU_MITIGATIONS=y
+CONFIG_CPU_NEEDS_NO_SMARTMIPS_OR_MICROMIPS=y
+CONFIG_CPU_R4K_CACHE_TLB=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_SUPPORTS_32BIT_KERNEL=y
+CONFIG_CPU_SUPPORTS_HIGHMEM=y
+CONFIG_CPU_SUPPORTS_MSA=y
+CONFIG_CRC16=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_HASH_INFO=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_GF128MUL=y
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=2
+CONFIG_CRYPTO_LIB_SHA1=y
+CONFIG_CRYPTO_LIB_UTILS=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_ZBOOT=y
+CONFIG_DMA_NEED_SYNC=y
+CONFIG_DMA_NONCOHERENT=y
+CONFIG_DTB_ECONET_NONE=y
+CONFIG_DTC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_EARLY_PRINTK_8250=y
+CONFIG_ECONET=y
+CONFIG_ECONET_EN751221_TIMER=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_FS_IOMAP=y
+CONFIG_FUNCTION_ALIGNMENT=0
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_GENERIC_ATOMIC64=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IOMAP=y
+CONFIG_GENERIC_IRQ_CHIP=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_LIB_ASHLDI3=y
+CONFIG_GENERIC_LIB_ASHRDI3=y
+CONFIG_GENERIC_LIB_CMPDI2=y
+CONFIG_GENERIC_LIB_LSHRDI3=y
+CONFIG_GENERIC_LIB_UCMPDI2=y
+CONFIG_GENERIC_MSI_IRQ=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_PHY=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GPIOLIB_IRQCHIP=y
+CONFIG_GPIO_CDEV=y
+CONFIG_GPIO_EN7523=y
+CONFIG_GPIO_GENERIC=y
+CONFIG_HARDWARE_WATCHPOINTS=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_MIPS_CPU=y
+CONFIG_IRQ_WORK=y
+# CONFIG_JFFS2_FS is not set
+CONFIG_LEDS_GPIO=y
+CONFIG_LIBFDT=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MIGRATION=y
+CONFIG_MIPS=y
+CONFIG_MIPS_ASID_BITS=8
+CONFIG_MIPS_ASID_SHIFT=0
+CONFIG_MIPS_CM=y
+# CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER is not set
+CONFIG_MIPS_CMDLINE_FROM_DTB=y
+CONFIG_MIPS_CPC=y
+CONFIG_MIPS_CPS=y
+# CONFIG_MIPS_CPS_NS16550_BOOL is not set
+CONFIG_MIPS_CPU_SCACHE=y
+CONFIG_MIPS_GIC=y
+CONFIG_MIPS_L1_CACHE_SHIFT=5
+CONFIG_MIPS_MT=y
+CONFIG_MIPS_MT_FPAFF=y
+CONFIG_MIPS_MT_SMP=y
+# CONFIG_MIPS_NO_APPENDED_DTB is not set
+CONFIG_MIPS_NR_CPU_NR_MAP=4
+CONFIG_MIPS_PERF_SHARED_TC_COUNTERS=y
+CONFIG_MIPS_RAW_APPENDED_DTB=y
+CONFIG_MIPS_SPRAM=y
+CONFIG_MMU_LAZY_TLB_REFCOUNT=y
+CONFIG_MODULES_USE_ELF_REL=y
+CONFIG_MTD_NAND_CORE=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_MTK_BMT=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=13
+CONFIG_MTD_UBI_BLOCK=y
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEED_SRCU_NMI_SAFE=y
+CONFIG_NET_EGRESS=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_INGRESS=y
+CONFIG_NET_XGRESS=y
+CONFIG_NLS=y
+CONFIG_NR_CPUS=4
+CONFIG_NVMEM=y
+CONFIG_NVMEM_LAYOUTS=y
+CONFIG_NVMEM_SYSFS=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+CONFIG_PCI=y
+CONFIG_PCIE_MEDIATEK=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DOMAINS_GENERIC=y
+CONFIG_PCI_DRIVERS_GENERIC=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_MSI_ARCH_FALLBACKS=y
+CONFIG_PERF_USE_VMALLOC=y
+CONFIG_PGTABLE_LEVELS=2
+CONFIG_PHY_EN7528_PCIE=y
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RATIONAL=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_MMIO=y
+CONFIG_RESET_CONTROLLER=y
+CONFIG_RFS_ACCEL=y
+CONFIG_RPS=y
+# CONFIG_SCHED_CORE is not set
+CONFIG_SCHED_SMT=y
+CONFIG_SERIAL_8250_AIROHA=y
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SGL_ALLOC=y
+CONFIG_SMP=y
+CONFIG_SMP_UP=y
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+# CONFIG_SOC_ECONET_EN751221 is not set
+CONFIG_SOC_ECONET_EN7528=y
+CONFIG_SPI=y
+# CONFIG_SPI_AIROHA_EN7523 is not set
+CONFIG_SPI_AIROHA_SNFI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+CONFIG_SPLIT_PTE_PTLOCKS=y
+CONFIG_SYNC_R4K=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_SYS_HAS_CPU_MIPS32_R1=y
+CONFIG_SYS_HAS_CPU_MIPS32_R2=y
+CONFIG_SYS_HAS_EARLY_PRINTK=y
+CONFIG_SYS_SUPPORTS_32BIT_KERNEL=y
+CONFIG_SYS_SUPPORTS_ARBIT_HZ=y
+CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
+CONFIG_SYS_SUPPORTS_HIGHMEM=y
+CONFIG_SYS_SUPPORTS_HOTPLUG_CPU=y
+CONFIG_SYS_SUPPORTS_LITTLE_ENDIAN=y
+CONFIG_SYS_SUPPORTS_MIPS16=y
+CONFIG_SYS_SUPPORTS_MIPS_CPS=y
+CONFIG_SYS_SUPPORTS_MULTITHREADING=y
+CONFIG_SYS_SUPPORTS_SCHED_SMT=y
+CONFIG_SYS_SUPPORTS_SMP=y
+CONFIG_SYS_SUPPORTS_ZBOOT=y
+CONFIG_SYS_SUPPORTS_ZBOOT_UART16550=y
+CONFIG_TARGET_ISA_REV=2
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UBIFS_FS=y
+CONFIG_USB=y
+CONFIG_USB_COMMON=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MTK=y
+# CONFIG_USB_XHCI_PLATFORM is not set
+CONFIG_USE_GENERIC_EARLY_PRINTK_8250=y
+CONFIG_USE_OF=y
+CONFIG_WEAK_ORDERING=y
+CONFIG_XPS=y
+CONFIG_XXHASH=y
+CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZSTD_COMMON=y
+CONFIG_ZSTD_COMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
--- /dev/null
+CONFIG_ARCH_32BIT_OFF_T=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MMAP_RND_BITS_MAX=15
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=15
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_BLK_MQ_PCI=y
+CONFIG_BOARD_SCACHE=y
+CONFIG_CLKSRC_MMIO=y
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+CONFIG_COMMON_CLK_EN7523=y
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_CPU_GENERIC_DUMP_TLB=y
+CONFIG_CPU_HAS_DIEI=y
+CONFIG_CPU_HAS_PREFETCH=y
+CONFIG_CPU_HAS_RIXI=y
+CONFIG_CPU_HAS_SYNC=y
+CONFIG_CPU_LITTLE_ENDIAN=y
+CONFIG_CPU_MIPS32=y
+# CONFIG_CPU_MIPS32_R1 is not set
+CONFIG_CPU_MIPS32_R2=y
+CONFIG_CPU_MIPSR2=y
+CONFIG_CPU_MIPSR2_IRQ_EI=y
+CONFIG_CPU_MIPSR2_IRQ_VI=y
+CONFIG_CPU_MITIGATIONS=y
+CONFIG_CPU_NEEDS_NO_SMARTMIPS_OR_MICROMIPS=y
+CONFIG_CPU_R4K_CACHE_TLB=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_SUPPORTS_32BIT_KERNEL=y
+CONFIG_CPU_SUPPORTS_HIGHMEM=y
+CONFIG_CPU_SUPPORTS_MSA=y
+CONFIG_CRC16=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_HASH_INFO=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_GF128MUL=y
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=2
+CONFIG_CRYPTO_LIB_UTILS=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_ZBOOT=y
+CONFIG_DMA_NEED_SYNC=y
+CONFIG_DMA_NONCOHERENT=y
+CONFIG_DTB_ECONET_NONE=y
+CONFIG_DTC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_EARLY_PRINTK_8250=y
+CONFIG_ECONET=y
+CONFIG_ECONET_EN751221_TIMER=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_FS_IOMAP=y
+CONFIG_FUNCTION_ALIGNMENT=0
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_GENERIC_ATOMIC64=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IOMAP=y
+CONFIG_GENERIC_IRQ_CHIP=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_LIB_ASHLDI3=y
+CONFIG_GENERIC_LIB_ASHRDI3=y
+CONFIG_GENERIC_LIB_CMPDI2=y
+CONFIG_GENERIC_LIB_LSHRDI3=y
+CONFIG_GENERIC_LIB_UCMPDI2=y
+CONFIG_GENERIC_MSI_IRQ=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_PHY=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GPIOLIB_IRQCHIP=y
+CONFIG_GPIO_CDEV=y
+CONFIG_GPIO_EN7523=y
+CONFIG_GPIO_GENERIC=y
+CONFIG_HARDWARE_WATCHPOINTS=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_MIPS_CPU=y
+CONFIG_IRQ_WORK=y
+# CONFIG_JFFS2_FS is not set
+CONFIG_LEDS_GPIO=y
+CONFIG_LIBFDT=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MIGRATION=y
+CONFIG_MIPS=y
+CONFIG_MIPS_ASID_BITS=8
+CONFIG_MIPS_ASID_SHIFT=0
+CONFIG_MIPS_CM=y
+# CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER is not set
+CONFIG_MIPS_CMDLINE_FROM_DTB=y
+CONFIG_MIPS_CPC=y
+CONFIG_MIPS_CPS=y
+# CONFIG_MIPS_CPS_NS16550_BOOL is not set
+CONFIG_MIPS_CPU_SCACHE=y
+CONFIG_MIPS_GIC=y
+CONFIG_MIPS_L1_CACHE_SHIFT=5
+CONFIG_MIPS_MT=y
+CONFIG_MIPS_MT_FPAFF=y
+CONFIG_MIPS_MT_SMP=y
+# CONFIG_MIPS_NO_APPENDED_DTB is not set
+CONFIG_MIPS_NR_CPU_NR_MAP=4
+CONFIG_MIPS_PERF_SHARED_TC_COUNTERS=y
+CONFIG_MIPS_RAW_APPENDED_DTB=y
+CONFIG_MIPS_SPRAM=y
+CONFIG_MMU_LAZY_TLB_REFCOUNT=y
+CONFIG_MODULES_USE_ELF_REL=y
+CONFIG_MTD_NAND_CORE=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_MTK_BMT=y
+CONFIG_MTD_SPI_NAND=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=13
+CONFIG_MTD_UBI_BLOCK=y
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEED_SRCU_NMI_SAFE=y
+CONFIG_NET_EGRESS=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_INGRESS=y
+CONFIG_NET_XGRESS=y
+CONFIG_NLS=y
+CONFIG_NR_CPUS=4
+CONFIG_NVMEM=y
+CONFIG_NVMEM_LAYOUTS=y
+CONFIG_NVMEM_SYSFS=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+CONFIG_PCI=y
+CONFIG_PCIE_MEDIATEK=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DOMAINS_GENERIC=y
+CONFIG_PCI_DRIVERS_GENERIC=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_MSI_ARCH_FALLBACKS=y
+CONFIG_PERF_USE_VMALLOC=y
+CONFIG_PGTABLE_LEVELS=2
+CONFIG_PHY_EN7528_PCIE=y
+CONFIG_PHY_EN7528_USB=y
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RATIONAL=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_MMIO=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_RESET_CONTROLLER=y
+CONFIG_RFS_ACCEL=y
+CONFIG_RPS=y
+# CONFIG_SCHED_CORE is not set
+CONFIG_SCHED_SMT=y
+CONFIG_SERIAL_8250_AIROHA=y
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SGL_ALLOC=y
+CONFIG_SMP=y
+CONFIG_SMP_UP=y
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+# CONFIG_SOC_ECONET_EN751221 is not set
+CONFIG_SOC_ECONET_EN7528=y
+CONFIG_SPI=y
+# CONFIG_SPI_AIROHA_EN7523 is not set
+CONFIG_SPI_AIROHA_SNFI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+CONFIG_SPLIT_PTE_PTLOCKS=y
+CONFIG_SYNC_R4K=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_SYS_HAS_CPU_MIPS32_R1=y
+CONFIG_SYS_HAS_CPU_MIPS32_R2=y
+CONFIG_SYS_HAS_EARLY_PRINTK=y
+CONFIG_SYS_SUPPORTS_32BIT_KERNEL=y
+CONFIG_SYS_SUPPORTS_ARBIT_HZ=y
+CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
+CONFIG_SYS_SUPPORTS_HIGHMEM=y
+CONFIG_SYS_SUPPORTS_HOTPLUG_CPU=y
+CONFIG_SYS_SUPPORTS_LITTLE_ENDIAN=y
+CONFIG_SYS_SUPPORTS_MIPS16=y
+CONFIG_SYS_SUPPORTS_MIPS_CPS=y
+CONFIG_SYS_SUPPORTS_MULTITHREADING=y
+CONFIG_SYS_SUPPORTS_SCHED_SMT=y
+CONFIG_SYS_SUPPORTS_SMP=y
+CONFIG_SYS_SUPPORTS_ZBOOT=y
+CONFIG_SYS_SUPPORTS_ZBOOT_UART16550=y
+CONFIG_TARGET_ISA_REV=2
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UBIFS_FS=y
+CONFIG_USB=y
+CONFIG_USB_COMMON=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MTK=y
+# CONFIG_USB_XHCI_PLATFORM is not set
+CONFIG_USE_GENERIC_EARLY_PRINTK_8250=y
+CONFIG_USE_OF=y
+CONFIG_WEAK_ORDERING=y
+CONFIG_XPS=y
+CONFIG_XXHASH=y
+CONFIG_ZBOOT_LOAD_ADDRESS=0x80020000
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZSTD_COMMON=y
+CONFIG_ZSTD_COMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
--- /dev/null
+From 9773c540441c6ae15aefb49e67142e94369dbbc0 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Sun, 30 Mar 2025 17:02:58 +0000
+Subject: [PATCH] dt-bindings: interrupt-controller: Add EcoNet EN751221 INTC
+
+Document the device tree binding for the interrupt controller in the
+EcoNet EN751221 MIPS SoC.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
+Link: https://lore.kernel.org/all/20250330170306.2584136-3-cjd@cjdns.fr
+---
+ .../econet,en751221-intc.yaml | 78 +++++++++++++++++++
+ 1 file changed, 78 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
+@@ -0,0 +1,78 @@
++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/interrupt-controller/econet,en751221-intc.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: EcoNet EN751221 Interrupt Controller
++
++maintainers:
++ - Caleb James DeLisle <cjd@cjdns.fr>
++
++description:
++ The EcoNet EN751221 Interrupt Controller is a simple interrupt controller
++ designed for the MIPS 34Kc MT SMP processor with 2 VPEs. Each interrupt can
++ be routed to either VPE but not both, so to support per-CPU interrupts, a
++ secondary IRQ number is allocated to control masking/unmasking on VPE#1. For
++ lack of a better term we call these "shadow interrupts". The assignment of
++ shadow interrupts is defined by the SoC integrator when wiring the interrupt
++ lines, so they are configurable in the device tree.
++
++allOf:
++ - $ref: /schemas/interrupt-controller.yaml#
++
++properties:
++ compatible:
++ const: econet,en751221-intc
++
++ reg:
++ maxItems: 1
++
++ "#interrupt-cells":
++ const: 1
++
++ interrupt-controller: true
++
++ interrupts:
++ maxItems: 1
++ description: Interrupt line connecting this controller to its parent.
++
++ econet,shadow-interrupts:
++ $ref: /schemas/types.yaml#/definitions/uint32-matrix
++ description:
++ An array of interrupt number pairs where each pair represents a shadow
++ interrupt relationship. The first number in each pair is the primary IRQ,
++ and the second is its shadow IRQ used for VPE#1 control. For example,
++ <8 3> means IRQ 8 is shadowed by IRQ 3, so IRQ 3 cannot be mapped, but
++ when VPE#1 requests IRQ 8, it will manipulate the IRQ 3 mask bit.
++ minItems: 1
++ maxItems: 20
++ items:
++ items:
++ - description: primary per-CPU IRQ
++ - description: shadow IRQ number
++
++required:
++ - compatible
++ - reg
++ - interrupt-controller
++ - "#interrupt-cells"
++ - interrupts
++
++additionalProperties: false
++
++examples:
++ - |
++ interrupt-controller@1fb40000 {
++ compatible = "econet,en751221-intc";
++ reg = <0x1fb40000 0x100>;
++
++ interrupt-controller;
++ #interrupt-cells = <1>;
++
++ interrupt-parent = <&cpuintc>;
++ interrupts = <2>;
++
++ econet,shadow-interrupts = <7 2>, <8 3>, <13 12>, <30 29>;
++ };
++...
--- /dev/null
+From 1902a59cf5f9d8b99ecf0cb8f122cb00ef7a3f13 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Sun, 30 Mar 2025 17:02:59 +0000
+Subject: [PATCH] irqchip: Add EcoNet EN751221 INTC
+
+Add a driver for the interrupt controller in the EcoNet EN751221 MIPS SoC.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Link: https://lore.kernel.org/all/20250330170306.2584136-4-cjd@cjdns.fr
+---
+ drivers/irqchip/Kconfig | 5 +
+ drivers/irqchip/Makefile | 1 +
+ drivers/irqchip/irq-econet-en751221.c | 309 ++++++++++++++++++++++++++
+ 3 files changed, 315 insertions(+)
+ create mode 100644 drivers/irqchip/irq-econet-en751221.c
+
+--- a/drivers/irqchip/Kconfig
++++ b/drivers/irqchip/Kconfig
+@@ -148,6 +148,11 @@ config DW_APB_ICTL
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN_HIERARCHY
+
++config ECONET_EN751221_INTC
++ bool
++ select GENERIC_IRQ_CHIP
++ select IRQ_DOMAIN
++
+ config FARADAY_FTINTC010
+ bool
+ select IRQ_DOMAIN
+--- a/drivers/irqchip/Makefile
++++ b/drivers/irqchip/Makefile
+@@ -10,6 +10,7 @@ obj-$(CONFIG_ARCH_BCM2835) += irq-bcm28
+ obj-$(CONFIG_ARCH_ACTIONS) += irq-owl-sirq.o
+ obj-$(CONFIG_DAVINCI_CP_INTC) += irq-davinci-cp-intc.o
+ obj-$(CONFIG_EXYNOS_IRQ_COMBINER) += exynos-combiner.o
++obj-$(CONFIG_ECONET_EN751221_INTC) += irq-econet-en751221.o
+ obj-$(CONFIG_FARADAY_FTINTC010) += irq-ftintc010.o
+ obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o
+ obj-$(CONFIG_ARCH_LPC32XX) += irq-lpc32xx.o
+--- /dev/null
++++ b/drivers/irqchip/irq-econet-en751221.c
+@@ -0,0 +1,309 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * EN751221 Interrupt Controller Driver.
++ *
++ * The EcoNet EN751221 Interrupt Controller is a simple interrupt controller
++ * designed for the MIPS 34Kc MT SMP processor with 2 VPEs. Each interrupt can
++ * be routed to either VPE but not both, so to support per-CPU interrupts, a
++ * secondary IRQ number is allocated to control masking/unmasking on VPE#1. In
++ * this driver, these are called "shadow interrupts". The assignment of shadow
++ * interrupts is defined by the SoC integrator when wiring the interrupt lines,
++ * so they are configurable in the device tree.
++ *
++ * If an interrupt (say 30) needs per-CPU capability, the SoC integrator
++ * allocates another IRQ number (say 29) to be its shadow. The device tree
++ * reflects this by adding the pair <30 29> to the "econet,shadow-interrupts"
++ * property.
++ *
++ * When VPE#1 requests IRQ 30, the driver manipulates the mask bit for IRQ 29,
++ * telling the hardware to mask VPE#1's view of IRQ 30.
++ *
++ * Copyright (C) 2025 Caleb James DeLisle <cjd@cjdns.fr>
++ */
++
++#include <linux/cleanup.h>
++#include <linux/io.h>
++#include <linux/of.h>
++#include <linux/of_address.h>
++#include <linux/of_irq.h>
++#include <linux/irqdomain.h>
++#include <linux/irqchip.h>
++#include <linux/irqchip/chained_irq.h>
++
++#define IRQ_COUNT 40
++
++#define NOT_PERCPU 0xff
++#define IS_SHADOW 0xfe
++
++#define REG_MASK0 0x04
++#define REG_MASK1 0x50
++#define REG_PENDING0 0x08
++#define REG_PENDING1 0x54
++
++/**
++ * @membase: Base address of the interrupt controller registers
++ * @interrupt_shadows: Array of all interrupts, for each value,
++ * - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
++ * - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
++ * - else: This is a per-cpu interrupt whose shadow is the value
++ */
++static struct {
++ void __iomem *membase;
++ u8 interrupt_shadows[IRQ_COUNT];
++} econet_intc __ro_after_init;
++
++static DEFINE_RAW_SPINLOCK(irq_lock);
++
++/* IRQs must be disabled */
++static void econet_wreg(u32 reg, u32 val, u32 mask)
++{
++ u32 v;
++
++ guard(raw_spinlock)(&irq_lock);
++
++ v = ioread32(econet_intc.membase + reg);
++ v &= ~mask;
++ v |= val & mask;
++ iowrite32(v, econet_intc.membase + reg);
++}
++
++/* IRQs must be disabled */
++static void econet_chmask(u32 hwirq, bool unmask)
++{
++ u32 reg, mask;
++ u8 shadow;
++
++ /*
++ * If the IRQ is a shadow, it should never be manipulated directly.
++ * It should only be masked/unmasked as a result of the "real" per-cpu
++ * irq being manipulated by a thread running on VPE#1.
++ * If it is per-cpu (has a shadow), and we're on VPE#1, the shadow is what we mask.
++ * This is single processor only, so smp_processor_id() never exceeds 1.
++ */
++ shadow = econet_intc.interrupt_shadows[hwirq];
++ if (WARN_ON_ONCE(shadow == IS_SHADOW))
++ return;
++ else if (shadow != NOT_PERCPU && smp_processor_id() == 1)
++ hwirq = shadow;
++
++ if (hwirq >= 32) {
++ reg = REG_MASK1;
++ mask = BIT(hwirq - 32);
++ } else {
++ reg = REG_MASK0;
++ mask = BIT(hwirq);
++ }
++
++ econet_wreg(reg, unmask ? mask : 0, mask);
++}
++
++/* IRQs must be disabled */
++static void econet_intc_mask(struct irq_data *d)
++{
++ econet_chmask(d->hwirq, false);
++}
++
++/* IRQs must be disabled */
++static void econet_intc_unmask(struct irq_data *d)
++{
++ econet_chmask(d->hwirq, true);
++}
++
++static void econet_mask_all(void)
++{
++ /* IRQs are generally disabled during init, but guarding here makes it non-obligatory. */
++ guard(irqsave)();
++ econet_wreg(REG_MASK0, 0, ~0);
++ econet_wreg(REG_MASK1, 0, ~0);
++}
++
++static void econet_intc_handle_pending(struct irq_domain *d, u32 pending, u32 offset)
++{
++ int hwirq;
++
++ while (pending) {
++ hwirq = fls(pending) - 1;
++ generic_handle_domain_irq(d, hwirq + offset);
++ pending &= ~BIT(hwirq);
++ }
++}
++
++static void econet_intc_from_parent(struct irq_desc *desc)
++{
++ struct irq_chip *chip = irq_desc_get_chip(desc);
++ struct irq_domain *domain;
++ u32 pending0, pending1;
++
++ chained_irq_enter(chip, desc);
++
++ pending0 = ioread32(econet_intc.membase + REG_PENDING0);
++ pending1 = ioread32(econet_intc.membase + REG_PENDING1);
++
++ if (unlikely(!(pending0 | pending1))) {
++ spurious_interrupt();
++ } else {
++ domain = irq_desc_get_handler_data(desc);
++ econet_intc_handle_pending(domain, pending0, 0);
++ econet_intc_handle_pending(domain, pending1, 32);
++ }
++
++ chained_irq_exit(chip, desc);
++}
++
++static const struct irq_chip econet_irq_chip;
++
++static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
++{
++ int ret;
++
++ if (hwirq >= IRQ_COUNT) {
++ pr_err("%s: hwirq %lu out of range\n", __func__, hwirq);
++ return -EINVAL;
++ } else if (econet_intc.interrupt_shadows[hwirq] == IS_SHADOW) {
++ pr_err("%s: can't map hwirq %lu, it is a shadow interrupt\n", __func__, hwirq);
++ return -EINVAL;
++ }
++
++ if (econet_intc.interrupt_shadows[hwirq] == NOT_PERCPU) {
++ irq_set_chip_and_handler(irq, &econet_irq_chip, handle_level_irq);
++ } else {
++ irq_set_chip_and_handler(irq, &econet_irq_chip, handle_percpu_devid_irq);
++ ret = irq_set_percpu_devid(irq);
++ if (ret)
++ pr_warn("%s: Failed irq_set_percpu_devid for %u: %d\n", d->name, irq, ret);
++ }
++
++ irq_set_chip_data(irq, NULL);
++ return 0;
++}
++
++static const struct irq_chip econet_irq_chip = {
++ .name = "en751221-intc",
++ .irq_unmask = econet_intc_unmask,
++ .irq_mask = econet_intc_mask,
++ .irq_mask_ack = econet_intc_mask,
++};
++
++static const struct irq_domain_ops econet_domain_ops = {
++ .xlate = irq_domain_xlate_onecell,
++ .map = econet_intc_map
++};
++
++static int __init get_shadow_interrupts(struct device_node *node)
++{
++ const char *field = "econet,shadow-interrupts";
++ int num_shadows;
++
++ num_shadows = of_property_count_u32_elems(node, field);
++
++ memset(econet_intc.interrupt_shadows, NOT_PERCPU,
++ sizeof(econet_intc.interrupt_shadows));
++
++ if (num_shadows <= 0) {
++ return 0;
++ } else if (num_shadows % 2) {
++ pr_err("%pOF: %s count is odd, ignoring\n", node, field);
++ return 0;
++ }
++
++ u32 *shadows __free(kfree) = kmalloc_array(num_shadows, sizeof(u32), GFP_KERNEL);
++ if (!shadows)
++ return -ENOMEM;
++
++ if (of_property_read_u32_array(node, field, shadows, num_shadows)) {
++ pr_err("%pOF: Failed to read %s\n", node, field);
++ return -EINVAL;
++ }
++
++ for (int i = 0; i < num_shadows; i += 2) {
++ u32 shadow = shadows[i + 1];
++ u32 target = shadows[i];
++
++ if (shadow > IRQ_COUNT) {
++ pr_err("%pOF: %s[%d] shadow(%d) out of range\n",
++ node, field, i + 1, shadow);
++ continue;
++ }
++
++ if (target >= IRQ_COUNT) {
++ pr_err("%pOF: %s[%d] target(%d) out of range\n", node, field, i, target);
++ continue;
++ }
++
++ if (econet_intc.interrupt_shadows[target] != NOT_PERCPU) {
++ pr_err("%pOF: %s[%d] target(%d) already has a shadow\n",
++ node, field, i, target);
++ continue;
++ }
++
++ if (econet_intc.interrupt_shadows[shadow] != NOT_PERCPU) {
++ pr_err("%pOF: %s[%d] shadow(%d) already has a target\n",
++ node, field, i + 1, shadow);
++ continue;
++ }
++
++ econet_intc.interrupt_shadows[target] = shadow;
++ econet_intc.interrupt_shadows[shadow] = IS_SHADOW;
++ }
++
++ return 0;
++}
++
++static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
++{
++ struct irq_domain *domain;
++ struct resource res;
++ int ret, irq;
++
++ ret = get_shadow_interrupts(node);
++ if (ret)
++ return ret;
++
++ irq = irq_of_parse_and_map(node, 0);
++ if (!irq) {
++ pr_err("%pOF: DT: Failed to get IRQ from 'interrupts'\n", node);
++ return -EINVAL;
++ }
++
++ if (of_address_to_resource(node, 0, &res)) {
++ pr_err("%pOF: DT: Failed to get 'reg'\n", node);
++ ret = -EINVAL;
++ goto err_dispose_mapping;
++ }
++
++ if (!request_mem_region(res.start, resource_size(&res), res.name)) {
++ pr_err("%pOF: Failed to request memory\n", node);
++ ret = -EBUSY;
++ goto err_dispose_mapping;
++ }
++
++ econet_intc.membase = ioremap(res.start, resource_size(&res));
++ if (!econet_intc.membase) {
++ pr_err("%pOF: Failed to remap membase\n", node);
++ ret = -ENOMEM;
++ goto err_release;
++ }
++
++ econet_mask_all();
++
++ domain = irq_domain_add_linear(node, IRQ_COUNT, &econet_domain_ops, NULL);
++ if (!domain) {
++ pr_err("%pOF: Failed to add irqdomain\n", node);
++ ret = -ENOMEM;
++ goto err_unmap;
++ }
++
++ irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
++
++ return 0;
++
++err_unmap:
++ iounmap(econet_intc.membase);
++err_release:
++ release_mem_region(res.start, resource_size(&res));
++err_dispose_mapping:
++ irq_dispose_mapping(irq);
++ return ret;
++}
++
++IRQCHIP_DECLARE(econet_en751221_intc, "econet,en751221-intc", econet_intc_of_init);
--- /dev/null
+From 9e0dd98654a528735d2b363d0dc73f7904108652 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Sun, 30 Mar 2025 17:02:57 +0000
+Subject: [PATCH] dt-bindings: vendor-prefixes: Add EcoNet
+
+Add the "econet" vendor prefix for SoC maker
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://lore.kernel.org/r/20250330170306.2584136-2-cjd@cjdns.fr
+Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
+---
+ Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
++++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
+@@ -420,6 +420,8 @@ patternProperties:
+ description: EBV Elektronik
+ "^eckelmann,.*":
+ description: Eckelmann AG
++ "^econet,.*":
++ description: EcoNet (HK) Limited
+ "^edgeble,.*":
+ description: Edgeble AI Technologies Pvt. Ltd.
+ "^edimax,.*":
--- /dev/null
+From 30fddbd5325459102e448c9a26a1bc15ef563381 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:44:54 +0000
+Subject: [PATCH] dt-bindings: timer: Add EcoNet EN751221 "HPT" CPU Timer
+
+Add device tree bindings for the so-called high-precision timer (HPT)
+in the EcoNet EN751221 SoC.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://lore.kernel.org/r/20250507134500.390547-2-cjd@cjdns.fr
+Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
+---
+ .../bindings/timer/econet,en751221-timer.yaml | 80 +++++++++++++++++++
+ 1 file changed, 80 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
+@@ -0,0 +1,80 @@
++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/timer/econet,en751221-timer.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: EcoNet EN751221 High Precision Timer (HPT)
++
++maintainers:
++ - Caleb James DeLisle <cjd@cjdns.fr>
++
++description:
++ The EcoNet High Precision Timer (HPT) is a timer peripheral found in various
++ EcoNet SoCs, including the EN751221 and EN751627 families. It provides per-VPE
++ count/compare registers and a per-CPU control register, with a single interrupt
++ line using a percpu-devid interrupt mechanism.
++
++properties:
++ compatible:
++ oneOf:
++ - const: econet,en751221-timer
++ - items:
++ - const: econet,en751627-timer
++ - const: econet,en751221-timer
++
++ reg:
++ minItems: 1
++ maxItems: 2
++
++ interrupts:
++ maxItems: 1
++ description: A percpu-devid timer interrupt shared across CPUs.
++
++ clocks:
++ maxItems: 1
++
++required:
++ - compatible
++ - reg
++ - interrupts
++ - clocks
++
++allOf:
++ - if:
++ properties:
++ compatible:
++ contains:
++ const: econet,en751627-timer
++ then:
++ properties:
++ reg:
++ items:
++ - description: VPE timers 0 and 1
++ - description: VPE timers 2 and 3
++ else:
++ properties:
++ reg:
++ items:
++ - description: VPE timers 0 and 1
++
++additionalProperties: false
++
++examples:
++ - |
++ timer@1fbf0400 {
++ compatible = "econet,en751627-timer", "econet,en751221-timer";
++ reg = <0x1fbf0400 0x100>, <0x1fbe0000 0x100>;
++ interrupt-parent = <&intc>;
++ interrupts = <30>;
++ clocks = <&hpt_clock>;
++ };
++ - |
++ timer@1fbf0400 {
++ compatible = "econet,en751221-timer";
++ reg = <0x1fbe0400 0x100>;
++ interrupt-parent = <&intc>;
++ interrupts = <30>;
++ clocks = <&hpt_clock>;
++ };
++...
--- /dev/null
+From 3b4c33ac87d0d11308f4445ecec2a124e2e77724 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:44:55 +0000
+Subject: [PATCH] clocksource/drivers: Add EcoNet Timer HPT driver
+
+Introduce a clocksource driver for the so-called high-precision timer (HPT)
+in the EcoNet EN751221 and EN751627 MIPS SoCs.
+
+It's a 32 bit upward-counting one-shot timer which relies on the crystal so it
+is unaffected by CPU power mode. On MIPS 34K devices (single core) there is
+one timer, and on 1004K devices (dual core) there are two.
+
+Each timer has two sets of count/compare registers so that there is one for
+each of the VPEs on the core. Because each core has 2 VPEs, register selection
+takes the CPU number / 2 for the timer corrisponding to the core, then CPU
+number % 2 for the register corrisponding to the VPE.
+
+These timers use a percpu-devid IRQ to route interrupts to the VPE which set
+the event.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Link: https://lore.kernel.org/r/20250507134500.390547-3-cjd@cjdns.fr
+Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
+---
+ drivers/clocksource/Kconfig | 8 +
+ drivers/clocksource/Makefile | 1 +
+ drivers/clocksource/timer-econet-en751221.c | 216 ++++++++++++++++++++
+ 3 files changed, 225 insertions(+)
+ create mode 100644 drivers/clocksource/timer-econet-en751221.c
+
+--- a/drivers/clocksource/Kconfig
++++ b/drivers/clocksource/Kconfig
+@@ -73,6 +73,14 @@ config DW_APB_TIMER_OF
+ select DW_APB_TIMER
+ select TIMER_OF
+
++config ECONET_EN751221_TIMER
++ bool "EcoNet EN751221 High Precision Timer" if COMPILE_TEST
++ depends on HAS_IOMEM
++ select CLKSRC_MMIO
++ select TIMER_OF
++ help
++ Support for CPU timer found on EcoNet MIPS based SoCs.
++
+ config FTTMR010_TIMER
+ bool "Faraday Technology timer driver" if COMPILE_TEST
+ depends on HAS_IOMEM
+--- a/drivers/clocksource/Makefile
++++ b/drivers/clocksource/Makefile
+@@ -17,6 +17,7 @@ obj-$(CONFIG_CLKBLD_I8253) += i8253.o
+ obj-$(CONFIG_CLKSRC_MMIO) += mmio.o
+ obj-$(CONFIG_DAVINCI_TIMER) += timer-davinci.o
+ obj-$(CONFIG_DIGICOLOR_TIMER) += timer-digicolor.o
++obj-$(CONFIG_ECONET_EN751221_TIMER) += timer-econet-en751221.o
+ obj-$(CONFIG_OMAP_DM_TIMER) += timer-ti-dm.o
+ obj-$(CONFIG_OMAP_DM_SYSTIMER) += timer-ti-dm-systimer.o
+ obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o
+--- /dev/null
++++ b/drivers/clocksource/timer-econet-en751221.c
+@@ -0,0 +1,216 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Timer present on EcoNet EN75xx MIPS based SoCs.
++ *
++ * Copyright (C) 2025 by Caleb James DeLisle <cjd@cjdns.fr>
++ */
++
++#include <linux/io.h>
++#include <linux/cpumask.h>
++#include <linux/interrupt.h>
++#include <linux/clockchips.h>
++#include <linux/sched_clock.h>
++#include <linux/of.h>
++#include <linux/of_irq.h>
++#include <linux/of_address.h>
++#include <linux/cpuhotplug.h>
++#include <linux/clk.h>
++
++#define ECONET_BITS 32
++#define ECONET_MIN_DELTA 0x00001000
++#define ECONET_MAX_DELTA GENMASK(ECONET_BITS - 2, 0)
++/* 34Kc hardware has 1 block and 1004Kc has 2. */
++#define ECONET_NUM_BLOCKS DIV_ROUND_UP(NR_CPUS, 2)
++
++static struct {
++ void __iomem *membase[ECONET_NUM_BLOCKS];
++ u32 freq_hz;
++} econet_timer __ro_after_init;
++
++static DEFINE_PER_CPU(struct clock_event_device, econet_timer_pcpu);
++
++/* Each memory block has 2 timers, the order of registers is:
++ * CTL, CMR0, CNT0, CMR1, CNT1
++ */
++static inline void __iomem *reg_ctl(u32 timer_n)
++{
++ return econet_timer.membase[timer_n >> 1];
++}
++
++static inline void __iomem *reg_compare(u32 timer_n)
++{
++ return econet_timer.membase[timer_n >> 1] + (timer_n & 1) * 0x08 + 0x04;
++}
++
++static inline void __iomem *reg_count(u32 timer_n)
++{
++ return econet_timer.membase[timer_n >> 1] + (timer_n & 1) * 0x08 + 0x08;
++}
++
++static inline u32 ctl_bit_enabled(u32 timer_n)
++{
++ return 1U << (timer_n & 1);
++}
++
++static inline u32 ctl_bit_pending(u32 timer_n)
++{
++ return 1U << ((timer_n & 1) + 16);
++}
++
++static bool cevt_is_pending(int cpu_id)
++{
++ return ioread32(reg_ctl(cpu_id)) & ctl_bit_pending(cpu_id);
++}
++
++static irqreturn_t cevt_interrupt(int irq, void *dev_id)
++{
++ struct clock_event_device *dev = this_cpu_ptr(&econet_timer_pcpu);
++ int cpu = cpumask_first(dev->cpumask);
++
++ /* Each VPE has its own events,
++ * so this will only happen on spurious interrupt.
++ */
++ if (!cevt_is_pending(cpu))
++ return IRQ_NONE;
++
++ iowrite32(ioread32(reg_count(cpu)), reg_compare(cpu));
++ dev->event_handler(dev);
++ return IRQ_HANDLED;
++}
++
++static int cevt_set_next_event(ulong delta, struct clock_event_device *dev)
++{
++ u32 next;
++ int cpu;
++
++ cpu = cpumask_first(dev->cpumask);
++ next = ioread32(reg_count(cpu)) + delta;
++ iowrite32(next, reg_compare(cpu));
++
++ if ((s32)(next - ioread32(reg_count(cpu))) < ECONET_MIN_DELTA / 2)
++ return -ETIME;
++
++ return 0;
++}
++
++static int cevt_init_cpu(uint cpu)
++{
++ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
++ u32 reg;
++
++ pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
++
++ reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
++ iowrite32(reg, reg_ctl(cpu));
++
++ enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
++
++ /* Do this last because it synchronously configures the timer */
++ clockevents_config_and_register(cd, econet_timer.freq_hz,
++ ECONET_MIN_DELTA, ECONET_MAX_DELTA);
++
++ return 0;
++}
++
++static u64 notrace sched_clock_read(void)
++{
++ /* Always read from clock zero no matter the CPU */
++ return (u64)ioread32(reg_count(0));
++}
++
++/* Init */
++
++static void __init cevt_dev_init(uint cpu)
++{
++ iowrite32(0, reg_count(cpu));
++ iowrite32(U32_MAX, reg_compare(cpu));
++}
++
++static int __init cevt_init(struct device_node *np)
++{
++ int i, irq, ret;
++
++ irq = irq_of_parse_and_map(np, 0);
++ if (irq <= 0) {
++ pr_err("%pOFn: irq_of_parse_and_map failed", np);
++ return -EINVAL;
++ }
++
++ ret = request_percpu_irq(irq, cevt_interrupt, np->name, &econet_timer_pcpu);
++
++ if (ret < 0) {
++ pr_err("%pOFn: IRQ %d setup failed (%d)\n", np, irq, ret);
++ goto err_unmap_irq;
++ }
++
++ for_each_possible_cpu(i) {
++ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
++
++ cd->rating = 310,
++ cd->features = CLOCK_EVT_FEAT_ONESHOT |
++ CLOCK_EVT_FEAT_C3STOP |
++ CLOCK_EVT_FEAT_PERCPU;
++ cd->set_next_event = cevt_set_next_event;
++ cd->irq = irq;
++ cd->cpumask = cpumask_of(i);
++ cd->name = np->name;
++
++ cevt_dev_init(i);
++ }
++
++ cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
++ "clockevents/econet/timer:starting",
++ cevt_init_cpu, NULL);
++ return 0;
++
++err_unmap_irq:
++ irq_dispose_mapping(irq);
++ return ret;
++}
++
++static int __init timer_init(struct device_node *np)
++{
++ int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
++ struct clk *clk;
++ int ret;
++
++ clk = of_clk_get(np, 0);
++ if (IS_ERR(clk)) {
++ pr_err("%pOFn: Failed to get CPU clock from DT %ld\n", np, PTR_ERR(clk));
++ return PTR_ERR(clk);
++ }
++
++ econet_timer.freq_hz = clk_get_rate(clk);
++
++ for (int i = 0; i < num_blocks; i++) {
++ econet_timer.membase[i] = of_iomap(np, i);
++ if (!econet_timer.membase[i]) {
++ pr_err("%pOFn: failed to map register [%d]\n", np, i);
++ return -ENXIO;
++ }
++ }
++
++ /* For clocksource purposes always read clock zero, whatever the CPU */
++ ret = clocksource_mmio_init(reg_count(0), np->name,
++ econet_timer.freq_hz, 301, ECONET_BITS,
++ clocksource_mmio_readl_up);
++ if (ret) {
++ pr_err("%pOFn: clocksource_mmio_init failed: %d", np, ret);
++ return ret;
++ }
++
++ ret = cevt_init(np);
++ if (ret < 0)
++ return ret;
++
++ sched_clock_register(sched_clock_read, ECONET_BITS,
++ econet_timer.freq_hz);
++
++ pr_info("%pOFn: using %u.%03u MHz high precision timer\n", np,
++ econet_timer.freq_hz / 1000000,
++ (econet_timer.freq_hz / 1000) % 1000);
++
++ return 0;
++}
++
++TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
--- /dev/null
+From be8b4173719a61fdd8379e86895d855775cf5f91 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:44:56 +0000
+Subject: [PATCH] dt-bindings: mips: Add EcoNet platform binding
+
+Document the top-level device tree binding for EcoNet MIPS-based SoCs.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ .../devicetree/bindings/mips/econet.yaml | 26 +++++++++++++++++++
+ 1 file changed, 26 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/mips/econet.yaml
+
+--- /dev/null
++++ b/Documentation/devicetree/bindings/mips/econet.yaml
+@@ -0,0 +1,26 @@
++# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/mips/econet.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: EcoNet MIPS SoCs
++
++maintainers:
++ - Caleb James DeLisle <cjd@cjdns.fr>
++
++properties:
++ $nodename:
++ const: '/'
++
++ compatible:
++ oneOf:
++ - description: Boards with EcoNet EN751221 family SoC
++ items:
++ - enum:
++ - smartfiber,xp8421-b
++ - const: econet,en751221
++
++additionalProperties: true
++
++...
--- /dev/null
+From 35fb26f94dfa1b291086b84b2421f957214824d1 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:44:57 +0000
+Subject: [PATCH] mips: Add EcoNet MIPS platform support
+
+Add platform support for EcoNet MIPS SoCs.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ arch/mips/Kbuild.platforms | 1 +
+ arch/mips/Kconfig | 25 +++++++++
+ arch/mips/boot/compressed/uart-16550.c | 5 ++
+ arch/mips/econet/Kconfig | 37 ++++++++++++
+ arch/mips/econet/Makefile | 2 +
+ arch/mips/econet/Platform | 5 ++
+ arch/mips/econet/init.c | 78 ++++++++++++++++++++++++++
+ 7 files changed, 153 insertions(+)
+ create mode 100644 arch/mips/econet/Kconfig
+ create mode 100644 arch/mips/econet/Makefile
+ create mode 100644 arch/mips/econet/Platform
+ create mode 100644 arch/mips/econet/init.c
+
+--- a/arch/mips/Kbuild.platforms
++++ b/arch/mips/Kbuild.platforms
+@@ -11,6 +11,7 @@ platform-$(CONFIG_CAVIUM_OCTEON_SOC) +=
+ platform-$(CONFIG_EYEQ) += mobileye/
+ platform-$(CONFIG_MIPS_COBALT) += cobalt/
+ platform-$(CONFIG_MACH_DECSTATION) += dec/
++platform-$(CONFIG_ECONET) += econet/
+ platform-$(CONFIG_MIPS_GENERIC) += generic/
+ platform-$(CONFIG_MACH_JAZZ) += jazz/
+ platform-$(CONFIG_LANTIQ) += lantiq/
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -388,6 +388,30 @@ config MACH_DECSTATION
+
+ otherwise choose R3000.
+
++config ECONET
++ bool "EcoNet MIPS family"
++ select BOOT_RAW
++ select CPU_BIG_ENDIAN
++ select DEBUG_ZBOOT
++ select EARLY_PRINTK_8250
++ select ECONET_EN751221_TIMER
++ select SERIAL_OF_PLATFORM
++ select SYS_SUPPORTS_BIG_ENDIAN
++ select SYS_HAS_CPU_MIPS32_R1
++ select SYS_HAS_CPU_MIPS32_R2
++ select SYS_HAS_EARLY_PRINTK
++ select SYS_SUPPORTS_32BIT_KERNEL
++ select SYS_SUPPORTS_MIPS16
++ select SYS_SUPPORTS_ZBOOT_UART16550
++ select USE_GENERIC_EARLY_PRINTK_8250
++ select USE_OF
++ help
++ EcoNet EN75xx MIPS devices are big endian MIPS machines used
++ in XPON (fiber) and DSL applications. They have SPI, PCI, USB,
++ GPIO, and Ethernet, with optional XPON, DSL, and VoIP DSP cores.
++ Don't confuse these with the Airoha ARM devices sometimes referred
++ to as "EcoNet", this family is for MIPS based devices only.
++
+ config MACH_JAZZ
+ bool "Jazz family of machines"
+ select ARC_MEMORY
+@@ -1017,6 +1041,7 @@ source "arch/mips/ath79/Kconfig"
+ source "arch/mips/bcm47xx/Kconfig"
+ source "arch/mips/bcm63xx/Kconfig"
+ source "arch/mips/bmips/Kconfig"
++source "arch/mips/econet/Kconfig"
+ source "arch/mips/generic/Kconfig"
+ source "arch/mips/ingenic/Kconfig"
+ source "arch/mips/jazz/Kconfig"
+--- a/arch/mips/boot/compressed/uart-16550.c
++++ b/arch/mips/boot/compressed/uart-16550.c
+@@ -20,6 +20,11 @@
+ #define PORT(offset) (CKSEG1ADDR(INGENIC_UART_BASE_ADDR) + (4 * offset))
+ #endif
+
++#ifdef CONFIG_ECONET
++#define EN75_UART_BASE 0x1fbf0003
++#define PORT(offset) (CKSEG1ADDR(EN75_UART_BASE) + (4 * (offset)))
++#endif
++
+ #ifndef IOTYPE
+ #define IOTYPE char
+ #endif
+--- /dev/null
++++ b/arch/mips/econet/Kconfig
+@@ -0,0 +1,37 @@
++# SPDX-License-Identifier: GPL-2.0
++if ECONET
++
++choice
++ prompt "EcoNet SoC selection"
++ default SOC_ECONET_EN751221
++ help
++ Select EcoNet MIPS SoC type. Individual SoCs within a family are
++ very similar, so is it enough to select the right family, and
++ then customize to the specific SoC using the device tree only.
++
++ config SOC_ECONET_EN751221
++ bool "EN751221 family"
++ select COMMON_CLK
++ select ECONET_EN751221_INTC
++ select IRQ_MIPS_CPU
++ select SMP
++ select SMP_UP
++ select SYS_SUPPORTS_SMP
++ help
++ The EN751221 family includes EN7512, RN7513, EN7521, EN7526.
++ They are based on single core MIPS 34Kc processors. To boot
++ this kernel, you will need a device tree such as
++ MIPS_RAW_APPENDED_DTB=y, and a root filesystem.
++endchoice
++
++choice
++ prompt "Devicetree selection"
++ default DTB_ECONET_NONE
++ help
++ Select the devicetree.
++
++ config DTB_ECONET_NONE
++ bool "None"
++endchoice
++
++endif
+--- /dev/null
++++ b/arch/mips/econet/Makefile
+@@ -0,0 +1,2 @@
++
++obj-y := init.o
+--- /dev/null
++++ b/arch/mips/econet/Platform
+@@ -0,0 +1,5 @@
++# To address a 7.2MB kernel size limit in the EcoNet SDK bootloader,
++# we put the load address well above where the bootloader loads and then use
++# zboot. So please set CONFIG_ZBOOT_LOAD_ADDRESS to the address where your
++# bootloader actually places the kernel.
++load-$(CONFIG_ECONET) += 0xffffffff81000000
+--- /dev/null
++++ b/arch/mips/econet/init.c
+@@ -0,0 +1,78 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * EcoNet setup code
++ *
++ * Copyright (C) 2025 Caleb James DeLisle <cjd@cjdns.fr>
++ */
++
++#include <linux/init.h>
++#include <linux/of_clk.h>
++#include <linux/irqchip.h>
++
++#include <asm/addrspace.h>
++#include <asm/io.h>
++#include <asm/bootinfo.h>
++#include <asm/time.h>
++#include <asm/prom.h>
++#include <asm/smp-ops.h>
++#include <asm/reboot.h>
++
++#define CR_AHB_RSTCR ((void __iomem *)CKSEG1ADDR(0x1fb00040))
++#define RESET BIT(31)
++
++#define UART_BASE CKSEG1ADDR(0x1fbf0003)
++#define UART_REG_SHIFT 2
++
++static void hw_reset(char *command)
++{
++ iowrite32(RESET, CR_AHB_RSTCR);
++}
++
++/* 1. Bring up early printk. */
++void __init prom_init(void)
++{
++ setup_8250_early_printk_port(UART_BASE, UART_REG_SHIFT, 0);
++ _machine_restart = hw_reset;
++}
++
++/* 2. Parse the DT and find memory */
++void __init plat_mem_setup(void)
++{
++ void *dtb;
++
++ set_io_port_base(KSEG1);
++
++ dtb = get_fdt();
++ if (!dtb)
++ panic("no dtb found");
++
++ __dt_setup_arch(dtb);
++
++ early_init_dt_scan_memory();
++}
++
++/* 3. Overload __weak device_tree_init(), add SMP_UP ops */
++void __init device_tree_init(void)
++{
++ unflatten_and_copy_device_tree();
++
++ register_up_smp_ops();
++}
++
++const char *get_system_type(void)
++{
++ return "EcoNet-EN75xx";
++}
++
++/* 4. Initialize the IRQ subsystem */
++void __init arch_init_irq(void)
++{
++ irqchip_init();
++}
++
++/* 5. Timers */
++void __init plat_time_init(void)
++{
++ of_clk_init(NULL);
++ timer_probe();
++}
--- /dev/null
+From abc2d0bc2cb7c1412b8b254c0446f94b3e203c7c Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:44:58 +0000
+Subject: [PATCH] dt-bindings: vendor-prefixes: Add SmartFiber
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add "smartfiber" vendor prefix for manufactorer of EcoNet based boards.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
++++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
+@@ -1370,6 +1370,8 @@ patternProperties:
+ description: SKOV A/S
+ "^skyworks,.*":
+ description: Skyworks Solutions, Inc.
++ "^smartfiber,.*":
++ description: ShenZhen Smartfiber Technology Co, Ltd.
+ "^smartlabs,.*":
+ description: SmartLabs LLC
+ "^smartrg,.*":
--- /dev/null
+From 0ec4887009729297f7c10368084e41a8a9fbbd0e Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:44:59 +0000
+Subject: [PATCH] mips: dts: Add EcoNet DTS with EN751221 and SmartFiber
+ XP8421-B board
+
+Add DTS files in support of EcoNet platform, including SmartFiber XP8421-B,
+a low cost commercially available board based on EN751221.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ arch/mips/boot/dts/Makefile | 1 +
+ arch/mips/boot/dts/econet/Makefile | 2 +
+ arch/mips/boot/dts/econet/en751221.dtsi | 67 +++++++++++++++++++
+ .../econet/en751221_smartfiber_xp8421-b.dts | 19 ++++++
+ arch/mips/econet/Kconfig | 11 +++
+ 5 files changed, 100 insertions(+)
+ create mode 100644 arch/mips/boot/dts/econet/Makefile
+ create mode 100644 arch/mips/boot/dts/econet/en751221.dtsi
+ create mode 100644 arch/mips/boot/dts/econet/en751221_smartfiber_xp8421-b.dts
+
+--- a/arch/mips/boot/dts/Makefile
++++ b/arch/mips/boot/dts/Makefile
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0
+ subdir-$(CONFIG_BMIPS_GENERIC) += brcm
+ subdir-$(CONFIG_CAVIUM_OCTEON_SOC) += cavium-octeon
++subdir-$(CONFIG_ECONET) += econet
+ subdir-$(CONFIG_EYEQ) += mobileye
+ subdir-$(CONFIG_FIT_IMAGE_FDT_MARDUK) += img
+ subdir-$(CONFIG_FIT_IMAGE_FDT_BOSTON) += img
+--- /dev/null
++++ b/arch/mips/boot/dts/econet/Makefile
+@@ -0,0 +1,2 @@
++# SPDX-License-Identifier: GPL-2.0
++dtb-$(CONFIG_DTB_ECONET_SMARTFIBER_XP8421_B) += en751221_smartfiber_xp8421-b.dtb
+--- /dev/null
++++ b/arch/mips/boot/dts/econet/en751221.dtsi
+@@ -0,0 +1,67 @@
++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++/dts-v1/;
++
++/ {
++ compatible = "econet,en751221";
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ hpt_clock: clock {
++ compatible = "fixed-clock";
++ #clock-cells = <0>;
++ clock-frequency = <200000000>; /* 200 MHz */
++ };
++
++ cpus: cpus {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ cpu@0 {
++ device_type = "cpu";
++ compatible = "mips,mips24KEc";
++ reg = <0>;
++ };
++ };
++
++ cpuintc: interrupt-controller {
++ compatible = "mti,cpu-interrupt-controller";
++ interrupt-controller;
++ #address-cells = <0>;
++ #interrupt-cells = <1>;
++ };
++
++ intc: interrupt-controller@1fb40000 {
++ compatible = "econet,en751221-intc";
++ reg = <0x1fb40000 0x100>;
++ interrupt-parent = <&cpuintc>;
++ interrupts = <2>;
++
++ interrupt-controller;
++ #interrupt-cells = <1>;
++ econet,shadow-interrupts = <7 2>, <8 3>, <13 12>, <30 29>;
++ };
++
++ uart: serial@1fbf0000 {
++ compatible = "ns16550";
++ reg = <0x1fbf0000 0x30>;
++ reg-io-width = <4>;
++ reg-shift = <2>;
++ interrupt-parent = <&intc>;
++ interrupts = <0>;
++ /*
++ * Conversion of baud rate to clock frequency requires a
++ * computation that is not in the ns16550 driver, so this
++ * uart is fixed at 115200 baud.
++ */
++ clock-frequency = <1843200>;
++ };
++
++ timer_hpt: timer@1fbf0400 {
++ compatible = "econet,en751221-timer";
++ reg = <0x1fbf0400 0x100>;
++
++ interrupt-parent = <&intc>;
++ interrupts = <30>;
++ clocks = <&hpt_clock>;
++ };
++};
+--- /dev/null
++++ b/arch/mips/boot/dts/econet/en751221_smartfiber_xp8421-b.dts
+@@ -0,0 +1,19 @@
++// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
++/dts-v1/;
++
++#include "en751221.dtsi"
++
++/ {
++ model = "SmartFiber XP8421-B";
++ compatible = "smartfiber,xp8421-b", "econet,en751221";
++
++ memory@0 {
++ device_type = "memory";
++ reg = <0x00000000 0x1c000000>;
++ };
++
++ chosen {
++ stdout-path = "/serial@1fbf0000:115200";
++ linux,usable-memory-range = <0x00020000 0x1bfe0000>;
++ };
++};
+--- a/arch/mips/econet/Kconfig
++++ b/arch/mips/econet/Kconfig
+@@ -32,6 +32,17 @@ choice
+
+ config DTB_ECONET_NONE
+ bool "None"
++
++ config DTB_ECONET_SMARTFIBER_XP8421_B
++ bool "EN751221 SmartFiber XP8421-B"
++ depends on SOC_ECONET_EN751221
++ select BUILTIN_DTB
++ help
++ The SmartFiber XP8421-B is a device based on the EN751221 SoC.
++ It has 512MB of memory and 256MB of NAND flash. This kernel
++ needs only an appended initramfs to boot. It can be loaded
++ through XMODEM and booted from memory in the bootloader, or
++ it can be packed in tclinux.trx format and written to flash.
+ endchoice
+
+ endif
--- /dev/null
+From faefb0a59c5914b7b8f737e2ec5c82822e5bc4c7 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 7 May 2025 13:45:00 +0000
+Subject: [PATCH] MAINTAINERS: Add entry for newly added EcoNet platform.
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add a MAINTAINERS entry as part of integration of the EcoNet MIPS platform.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ MAINTAINERS | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -8025,6 +8025,18 @@ W: https://linuxtv.org
+ Q: http://patchwork.linuxtv.org/project/linux-media/list/
+ F: drivers/media/dvb-frontends/ec100*
+
++ECONET MIPS PLATFORM
++M: Caleb James DeLisle <cjd@cjdns.fr>
++L: linux-mips@vger.kernel.org
++S: Maintained
++F: Documentation/devicetree/bindings/interrupt-controller/econet,en751221-intc.yaml
++F: Documentation/devicetree/bindings/mips/econet.yaml
++F: Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
++F: arch/mips/boot/dts/econet/
++F: arch/mips/econet/
++F: drivers/clocksource/timer-econet-en751221.c
++F: drivers/irqchip/irq-econet-en751221.c
++
+ ECRYPT FILE SYSTEM
+ M: Tyler Hicks <code@tyhicks.com>
+ L: ecryptfs@vger.kernel.org
--- /dev/null
+From 79ee1d20e37cd553cc961962fca8107e69a0c293 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 21 May 2025 21:33:33 +0000
+Subject: [PATCH] mips: econet: Fix incorrect Kconfig dependencies
+
+config ECONET selects SERIAL_OF_PLATFORM and that depends on SERIAL_8250
+so we need to select SERIAL_8250 directly.
+Also do not enable DEBUG_ZBOOT unless DEBUG_KERNEL is set.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+Reported-by: kernel test robot <lkp@intel.com>
+Closes: https://lore.kernel.org/oe-kbuild-all/202505211654.CBdIsoTq-lkp@intel.com/
+Closes: https://lore.kernel.org/oe-kbuild-all/202505211451.WRjyf3a9-lkp@intel.com/
+Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
+---
+ arch/mips/Kconfig | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -392,9 +392,10 @@ config ECONET
+ bool "EcoNet MIPS family"
+ select BOOT_RAW
+ select CPU_BIG_ENDIAN
+- select DEBUG_ZBOOT
++ select DEBUG_ZBOOT if DEBUG_KERNEL
+ select EARLY_PRINTK_8250
+ select ECONET_EN751221_TIMER
++ select SERIAL_8250
+ select SERIAL_OF_PLATFORM
+ select SYS_SUPPORTS_BIG_ENDIAN
+ select SYS_HAS_CPU_MIPS32_R1
--- /dev/null
+From 82e6bf912d5846646892becea659b39d178d79e3 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 12 Nov 2024 01:08:53 +0100
+Subject: [PATCH 5/8] clk: en7523: move en7581_reset_register() in
+ en7581_clk_hw_init()
+
+Move en7581_reset_register routine in en7581_clk_hw_init() since reset
+feature is supported just by EN7581 SoC.
+Get rid of reset struct in en_clk_soc_data data struct.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/20241112-clk-en7581-syscon-v2-6-8ada5e394ae4@kernel.org
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-en7523.c | 93 ++++++++++++++--------------------------
+ 1 file changed, 33 insertions(+), 60 deletions(-)
+
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -76,11 +76,6 @@ struct en_rst_data {
+
+ struct en_clk_soc_data {
+ const struct clk_ops pcie_ops;
+- struct {
+- const u16 *bank_ofs;
+- const u16 *idx_map;
+- u16 idx_map_nr;
+- } reset;
+ int (*hw_init)(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data);
+ };
+@@ -596,32 +591,6 @@ static void en7581_register_clocks(struc
+ clk_data->num = EN7523_NUM_CLOCKS;
+ }
+
+-static int en7581_clk_hw_init(struct platform_device *pdev,
+- struct clk_hw_onecell_data *clk_data)
+-{
+- void __iomem *np_base;
+- struct regmap *map;
+- u32 val;
+-
+- map = syscon_regmap_lookup_by_compatible("airoha,en7581-chip-scu");
+- if (IS_ERR(map))
+- return PTR_ERR(map);
+-
+- np_base = devm_platform_ioremap_resource(pdev, 0);
+- if (IS_ERR(np_base))
+- return PTR_ERR(np_base);
+-
+- en7581_register_clocks(&pdev->dev, clk_data, map, np_base);
+-
+- val = readl(np_base + REG_NP_SCU_SSTR);
+- val &= ~(REG_PCIE_XSI0_SEL_MASK | REG_PCIE_XSI1_SEL_MASK);
+- writel(val, np_base + REG_NP_SCU_SSTR);
+- val = readl(np_base + REG_NP_SCU_PCIC);
+- writel(val | 3, np_base + REG_NP_SCU_PCIC);
+-
+- return 0;
+-}
+-
+ static int en7523_reset_update(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+ {
+@@ -671,23 +640,18 @@ static int en7523_reset_xlate(struct res
+ return rst_data->idx_map[reset_spec->args[0]];
+ }
+
+-static const struct reset_control_ops en7523_reset_ops = {
++static const struct reset_control_ops en7581_reset_ops = {
+ .assert = en7523_reset_assert,
+ .deassert = en7523_reset_deassert,
+ .status = en7523_reset_status,
+ };
+
+-static int en7523_reset_register(struct platform_device *pdev,
+- const struct en_clk_soc_data *soc_data)
++static int en7581_reset_register(struct platform_device *pdev)
+ {
+ struct device *dev = &pdev->dev;
+ struct en_rst_data *rst_data;
+ void __iomem *base;
+
+- /* no reset lines available */
+- if (!soc_data->reset.idx_map_nr)
+- return 0;
+-
+ base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+@@ -696,13 +660,13 @@ static int en7523_reset_register(struct
+ if (!rst_data)
+ return -ENOMEM;
+
+- rst_data->bank_ofs = soc_data->reset.bank_ofs;
+- rst_data->idx_map = soc_data->reset.idx_map;
++ rst_data->bank_ofs = en7581_rst_ofs;
++ rst_data->idx_map = en7581_rst_map;
+ rst_data->base = base;
+
+- rst_data->rcdev.nr_resets = soc_data->reset.idx_map_nr;
++ rst_data->rcdev.nr_resets = ARRAY_SIZE(en7581_rst_map);
+ rst_data->rcdev.of_xlate = en7523_reset_xlate;
+- rst_data->rcdev.ops = &en7523_reset_ops;
++ rst_data->rcdev.ops = &en7581_reset_ops;
+ rst_data->rcdev.of_node = dev->of_node;
+ rst_data->rcdev.of_reset_n_cells = 1;
+ rst_data->rcdev.owner = THIS_MODULE;
+@@ -711,6 +675,32 @@ static int en7523_reset_register(struct
+ return devm_reset_controller_register(dev, &rst_data->rcdev);
+ }
+
++static int en7581_clk_hw_init(struct platform_device *pdev,
++ struct clk_hw_onecell_data *clk_data)
++{
++ void __iomem *np_base;
++ struct regmap *map;
++ u32 val;
++
++ map = syscon_regmap_lookup_by_compatible("airoha,en7581-chip-scu");
++ if (IS_ERR(map))
++ return PTR_ERR(map);
++
++ np_base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(np_base))
++ return PTR_ERR(np_base);
++
++ en7581_register_clocks(&pdev->dev, clk_data, map, np_base);
++
++ val = readl(np_base + REG_NP_SCU_SSTR);
++ val &= ~(REG_PCIE_XSI0_SEL_MASK | REG_PCIE_XSI1_SEL_MASK);
++ writel(val, np_base + REG_NP_SCU_SSTR);
++ val = readl(np_base + REG_NP_SCU_PCIC);
++ writel(val | 3, np_base + REG_NP_SCU_PCIC);
++
++ return en7581_reset_register(pdev);
++}
++
+ static int en7523_clk_probe(struct platform_device *pdev)
+ {
+ struct device_node *node = pdev->dev.of_node;
+@@ -729,19 +719,7 @@ static int en7523_clk_probe(struct platf
+ if (r)
+ return r;
+
+- r = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data);
+- if (r)
+- return dev_err_probe(&pdev->dev, r, "Could not register clock provider: %s\n",
+- pdev->name);
+-
+- r = en7523_reset_register(pdev, soc_data);
+- if (r) {
+- of_clk_del_provider(node);
+- return dev_err_probe(&pdev->dev, r, "Could not register reset controller: %s\n",
+- pdev->name);
+- }
+-
+- return 0;
++ return of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data);
+ }
+
+ static const struct en_clk_soc_data en7523_data = {
+@@ -759,11 +737,6 @@ static const struct en_clk_soc_data en75
+ .enable = en7581_pci_enable,
+ .disable = en7581_pci_disable,
+ },
+- .reset = {
+- .bank_ofs = en7581_rst_ofs,
+- .idx_map = en7581_rst_map,
+- .idx_map_nr = ARRAY_SIZE(en7581_rst_map),
+- },
+ .hw_init = en7581_clk_hw_init,
+ };
+
--- /dev/null
+From a9eaf305017a5ebe73ab34e85bd5414055a88f29 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 12 Nov 2024 01:08:54 +0100
+Subject: [PATCH 6/8] clk: en7523: map io region in a single block
+
+Map all clock-controller memory region in a single block.
+This patch does not introduce any backward incompatibility since the dts
+for EN7581 SoC is not upstream yet.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/20241112-clk-en7581-syscon-v2-7-8ada5e394ae4@kernel.org
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-en7523.c | 32 +++++++++++++-------------------
+ 1 file changed, 13 insertions(+), 19 deletions(-)
+
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -39,8 +39,8 @@
+ #define REG_PCIE_XSI1_SEL_MASK GENMASK(12, 11)
+ #define REG_CRYPTO_CLKSRC2 0x20c
+
+-#define REG_RST_CTRL2 0x00
+-#define REG_RST_CTRL1 0x04
++#define REG_RST_CTRL2 0x830
++#define REG_RST_CTRL1 0x834
+
+ struct en_clk_desc {
+ int id;
+@@ -646,15 +646,9 @@ static const struct reset_control_ops en
+ .status = en7523_reset_status,
+ };
+
+-static int en7581_reset_register(struct platform_device *pdev)
++static int en7581_reset_register(struct device *dev, void __iomem *base)
+ {
+- struct device *dev = &pdev->dev;
+ struct en_rst_data *rst_data;
+- void __iomem *base;
+-
+- base = devm_platform_ioremap_resource(pdev, 1);
+- if (IS_ERR(base))
+- return PTR_ERR(base);
+
+ rst_data = devm_kzalloc(dev, sizeof(*rst_data), GFP_KERNEL);
+ if (!rst_data)
+@@ -678,27 +672,27 @@ static int en7581_reset_register(struct
+ static int en7581_clk_hw_init(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data)
+ {
+- void __iomem *np_base;
+ struct regmap *map;
++ void __iomem *base;
+ u32 val;
+
+ map = syscon_regmap_lookup_by_compatible("airoha,en7581-chip-scu");
+ if (IS_ERR(map))
+ return PTR_ERR(map);
+
+- np_base = devm_platform_ioremap_resource(pdev, 0);
+- if (IS_ERR(np_base))
+- return PTR_ERR(np_base);
++ base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(base))
++ return PTR_ERR(base);
+
+- en7581_register_clocks(&pdev->dev, clk_data, map, np_base);
++ en7581_register_clocks(&pdev->dev, clk_data, map, base);
+
+- val = readl(np_base + REG_NP_SCU_SSTR);
++ val = readl(base + REG_NP_SCU_SSTR);
+ val &= ~(REG_PCIE_XSI0_SEL_MASK | REG_PCIE_XSI1_SEL_MASK);
+- writel(val, np_base + REG_NP_SCU_SSTR);
+- val = readl(np_base + REG_NP_SCU_PCIC);
+- writel(val | 3, np_base + REG_NP_SCU_PCIC);
++ writel(val, base + REG_NP_SCU_SSTR);
++ val = readl(base + REG_NP_SCU_PCIC);
++ writel(val | 3, base + REG_NP_SCU_PCIC);
+
+- return en7581_reset_register(pdev);
++ return en7581_reset_register(&pdev->dev, base);
+ }
+
+ static int en7523_clk_probe(struct platform_device *pdev)
--- /dev/null
+From 90d4e466c9ea2010f33880a36317a8486ccbe082 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Wed, 8 Jan 2025 10:50:43 +0100
+Subject: [PATCH 1/3] PCI: mediatek-gen3: Move reset delay in
+ mtk_pcie_en7581_power_up()
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Airoha EN7581 has a hw bug asserting/releasing PCIE_PE_RSTB signal
+causing occasional PCIe link down issues. In order to overcome the
+problem, PCIe block is reset using REG_PCI_CONTROL (0x88) and
+REG_RESET_CONTROL (0x834) registers available in the clock module
+running clk_bulk_prepare_enable() in mtk_pcie_en7581_power_up().
+
+In order to make the code more readable, move the wait for the time
+needed to complete the PCIe reset from en7581_pci_enable() to
+mtk_pcie_en7581_power_up().
+
+Reduce reset timeout from 250ms to the standard PCIE_T_PVPERL_MS value
+(100ms) since it has no impact on the driver behavior.
+
+Link: https://lore.kernel.org/r/20250108-pcie-en7581-fixes-v6-4-21ac939a3b9b@kernel.org
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+Acked-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-en7523.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -478,7 +478,6 @@ static int en7581_pci_enable(struct clk_
+ REG_PCI_CONTROL_PERSTOUT;
+ val = readl(np_base + REG_PCI_CONTROL);
+ writel(val | mask, np_base + REG_PCI_CONTROL);
+- msleep(250);
+
+ return 0;
+ }
--- /dev/null
+From e4a9748e7103c47e575459db2b6a77d14f34da2b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Jan 2025 00:10:02 +0100
+Subject: [PATCH 2/3] clk: en7523: Rework clock handling for different clock
+ numbers
+
+Airoha EN7581 SoC have additional clock compared to EN7523 but current
+driver permits to only support up to EN7523 clock numbers.
+
+To handle this, rework the clock handling and permit to declare the
+clocks number in match_data and alloca clk_data based on the compatible
+match_data.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://lore.kernel.org/r/20250113231030.6735-2-ansuelsmth@gmail.com
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-en7523.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -75,6 +75,7 @@ struct en_rst_data {
+ };
+
+ struct en_clk_soc_data {
++ u32 num_clocks;
+ const struct clk_ops pcie_ops;
+ int (*hw_init)(struct platform_device *pdev,
+ struct clk_hw_onecell_data *clk_data);
+@@ -503,8 +504,6 @@ static void en7523_register_clocks(struc
+ u32 rate;
+ int i;
+
+- clk_data->num = EN7523_NUM_CLOCKS;
+-
+ for (i = 0; i < ARRAY_SIZE(en7523_base_clks); i++) {
+ const struct en_clk_desc *desc = &en7523_base_clks[i];
+ u32 reg = desc->div_reg ? desc->div_reg : desc->base_reg;
+@@ -586,8 +585,6 @@ static void en7581_register_clocks(struc
+
+ hw = en7523_register_pcie_clk(dev, base);
+ clk_data->hws[EN7523_CLK_PCIE] = hw;
+-
+- clk_data->num = EN7523_NUM_CLOCKS;
+ }
+
+ static int en7523_reset_update(struct reset_controller_dev *rcdev,
+@@ -701,13 +698,15 @@ static int en7523_clk_probe(struct platf
+ struct clk_hw_onecell_data *clk_data;
+ int r;
+
++ soc_data = device_get_match_data(&pdev->dev);
++
+ clk_data = devm_kzalloc(&pdev->dev,
+- struct_size(clk_data, hws, EN7523_NUM_CLOCKS),
++ struct_size(clk_data, hws, soc_data->num_clocks),
+ GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+- soc_data = device_get_match_data(&pdev->dev);
++ clk_data->num = soc_data->num_clocks;
+ r = soc_data->hw_init(pdev, clk_data);
+ if (r)
+ return r;
+@@ -716,6 +715,7 @@ static int en7523_clk_probe(struct platf
+ }
+
+ static const struct en_clk_soc_data en7523_data = {
++ .num_clocks = ARRAY_SIZE(en7523_base_clks) + 1,
+ .pcie_ops = {
+ .is_enabled = en7523_pci_is_enabled,
+ .prepare = en7523_pci_prepare,
+@@ -725,6 +725,8 @@ static const struct en_clk_soc_data en75
+ };
+
+ static const struct en_clk_soc_data en7581_data = {
++ /* We increment num_clocks by 1 to account for additional PCIe clock */
++ .num_clocks = ARRAY_SIZE(en7581_base_clks) + 1,
+ .pcie_ops = {
+ .is_enabled = en7581_pci_is_enabled,
+ .enable = en7581_pci_enable,
--- /dev/null
+From bfe257f9780d8f77045a7da6ec959ee0659d2f98 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Jan 2025 00:10:05 +0100
+Subject: [PATCH 3/3] clk: en7523: Add clock for eMMC for EN7581
+
+Add clock for eMMC for EN7581. This is used to give info of the current
+eMMC source clock and to switch it from 200MHz or 150MHz.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Link: https://lore.kernel.org/r/20250113231030.6735-5-ansuelsmth@gmail.com
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-en7523.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -91,6 +91,7 @@ static const u32 emi7581_base[] = { 5400
+ static const u32 bus7581_base[] = { 600000000, 540000000 };
+ static const u32 npu7581_base[] = { 800000000, 750000000, 720000000, 600000000 };
+ static const u32 crypto_base[] = { 540000000, 480000000 };
++static const u32 emmc7581_base[] = { 200000000, 150000000 };
+
+ static const struct en_clk_desc en7523_base_clks[] = {
+ {
+@@ -281,6 +282,15 @@ static const struct en_clk_desc en7581_b
+ .base_shift = 0,
+ .base_values = crypto_base,
+ .n_base_values = ARRAY_SIZE(crypto_base),
++ }, {
++ .id = EN7581_CLK_EMMC,
++ .name = "emmc",
++
++ .base_reg = REG_CRYPTO_CLKSRC2,
++ .base_bits = 1,
++ .base_shift = 12,
++ .base_values = emmc7581_base,
++ .n_base_values = ARRAY_SIZE(emmc7581_base),
+ }
+ };
+
--- /dev/null
+From 1c0608d860db973ad09b5a9ccb19b76ae07622a3 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Mon, 10 Nov 2025 06:56:44 +0300
+Subject: [PATCH] clk: en7523: Add reset-controller support for EN7523 SoC
+
+Introduce reset API support to EN7523 clock driver. EN7523 uses the
+same reset logic as EN7581, so just reuse existing code.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/clk-en7523.c | 64 ++++++++++++++++++++++++++++++++++++----
+ 1 file changed, 59 insertions(+), 5 deletions(-)
+
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -9,6 +9,7 @@
+ #include <linux/regmap.h>
+ #include <linux/reset-controller.h>
+ #include <dt-bindings/clock/en7523-clk.h>
++#include <dt-bindings/reset/airoha,en7523-reset.h>
+ #include <dt-bindings/reset/airoha,en7581-reset.h>
+
+ #define RST_NR_PER_BANK 32
+@@ -299,6 +300,53 @@ static const u16 en7581_rst_ofs[] = {
+ REG_RST_CTRL1,
+ };
+
++static const u16 en7523_rst_map[] = {
++ /* RST_CTRL2 */
++ [EN7523_XPON_PHY_RST] = 0,
++ [EN7523_XSI_MAC_RST] = 7,
++ [EN7523_XSI_PHY_RST] = 8,
++ [EN7523_NPU_RST] = 9,
++ [EN7523_I2S_RST] = 10,
++ [EN7523_TRNG_RST] = 11,
++ [EN7523_TRNG_MSTART_RST] = 12,
++ [EN7523_DUAL_HSI0_RST] = 13,
++ [EN7523_DUAL_HSI1_RST] = 14,
++ [EN7523_HSI_RST] = 15,
++ [EN7523_DUAL_HSI0_MAC_RST] = 16,
++ [EN7523_DUAL_HSI1_MAC_RST] = 17,
++ [EN7523_HSI_MAC_RST] = 18,
++ [EN7523_WDMA_RST] = 19,
++ [EN7523_WOE0_RST] = 20,
++ [EN7523_WOE1_RST] = 21,
++ [EN7523_HSDMA_RST] = 22,
++ [EN7523_I2C2RBUS_RST] = 23,
++ [EN7523_TDMA_RST] = 24,
++ /* RST_CTRL1 */
++ [EN7523_PCM1_ZSI_ISI_RST] = RST_NR_PER_BANK + 0,
++ [EN7523_FE_PDMA_RST] = RST_NR_PER_BANK + 1,
++ [EN7523_FE_QDMA_RST] = RST_NR_PER_BANK + 2,
++ [EN7523_PCM_SPIWP_RST] = RST_NR_PER_BANK + 4,
++ [EN7523_CRYPTO_RST] = RST_NR_PER_BANK + 6,
++ [EN7523_TIMER_RST] = RST_NR_PER_BANK + 8,
++ [EN7523_PCM1_RST] = RST_NR_PER_BANK + 11,
++ [EN7523_UART_RST] = RST_NR_PER_BANK + 12,
++ [EN7523_GPIO_RST] = RST_NR_PER_BANK + 13,
++ [EN7523_GDMA_RST] = RST_NR_PER_BANK + 14,
++ [EN7523_I2C_MASTER_RST] = RST_NR_PER_BANK + 16,
++ [EN7523_PCM2_ZSI_ISI_RST] = RST_NR_PER_BANK + 17,
++ [EN7523_SFC_RST] = RST_NR_PER_BANK + 18,
++ [EN7523_UART2_RST] = RST_NR_PER_BANK + 19,
++ [EN7523_GDMP_RST] = RST_NR_PER_BANK + 20,
++ [EN7523_FE_RST] = RST_NR_PER_BANK + 21,
++ [EN7523_USB_HOST_P0_RST] = RST_NR_PER_BANK + 22,
++ [EN7523_GSW_RST] = RST_NR_PER_BANK + 23,
++ [EN7523_SFC2_PCM_RST] = RST_NR_PER_BANK + 25,
++ [EN7523_PCIE0_RST] = RST_NR_PER_BANK + 26,
++ [EN7523_PCIE1_RST] = RST_NR_PER_BANK + 27,
++ [EN7523_PCIE_HB_RST] = RST_NR_PER_BANK + 29,
++ [EN7523_XPON_MAC_RST] = RST_NR_PER_BANK + 31,
++};
++
+ static const u16 en7581_rst_map[] = {
+ /* RST_CTRL2 */
+ [EN7581_XPON_PHY_RST] = 0,
+@@ -357,6 +405,9 @@ static const u16 en7581_rst_map[] = {
+ [EN7581_XPON_MAC_RST] = RST_NR_PER_BANK + 31,
+ };
+
++static int en7581_reset_register(struct device *dev, void __iomem *base,
++ const u16 *rst_map, int nr_resets);
++
+ static u32 en7523_get_base_rate(const struct en_clk_desc *desc, u32 val)
+ {
+ if (!desc->base_bits)
+@@ -552,7 +603,8 @@ static int en7523_clk_hw_init(struct pla
+
+ en7523_register_clocks(&pdev->dev, clk_data, base, np_base);
+
+- return 0;
++ return en7581_reset_register(&pdev->dev, np_base, en7523_rst_map,
++ ARRAY_SIZE(en7523_rst_map));
+ }
+
+ static void en7581_register_clocks(struct device *dev, struct clk_hw_onecell_data *clk_data,
+@@ -652,7 +704,8 @@ static const struct reset_control_ops en
+ .status = en7523_reset_status,
+ };
+
+-static int en7581_reset_register(struct device *dev, void __iomem *base)
++static int en7581_reset_register(struct device *dev, void __iomem *base,
++ const u16 *rst_map, int nr_resets)
+ {
+ struct en_rst_data *rst_data;
+
+@@ -661,10 +714,10 @@ static int en7581_reset_register(struct
+ return -ENOMEM;
+
+ rst_data->bank_ofs = en7581_rst_ofs;
+- rst_data->idx_map = en7581_rst_map;
++ rst_data->idx_map = rst_map;
+ rst_data->base = base;
+
+- rst_data->rcdev.nr_resets = ARRAY_SIZE(en7581_rst_map);
++ rst_data->rcdev.nr_resets = nr_resets;
+ rst_data->rcdev.of_xlate = en7523_reset_xlate;
+ rst_data->rcdev.ops = &en7581_reset_ops;
+ rst_data->rcdev.of_node = dev->of_node;
+@@ -698,7 +751,8 @@ static int en7581_clk_hw_init(struct pla
+ val = readl(base + REG_NP_SCU_PCIC);
+ writel(val | 3, base + REG_NP_SCU_PCIC);
+
+- return en7581_reset_register(&pdev->dev, base);
++ return en7581_reset_register(&pdev->dev, base, en7581_rst_map,
++ ARRAY_SIZE(en7581_rst_map));
+ }
+
+ static int en7523_clk_probe(struct platform_device *pdev)
--- /dev/null
+From 947643509279a605a09959a06d332bf027e8be57 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Mon, 10 Nov 2025 06:56:43 +0300
+Subject: [PATCH] dt-bindings: clock: airoha: Add reset support to EN7523 clock
+ binding
+
+Introduce reset capability to EN7523 device-tree clock binding
+documentation.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml
++++ b/Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml
+@@ -64,8 +64,6 @@ allOf:
+ - description: scu base address
+ - description: misc scu base address
+
+- '#reset-cells': false
+-
+ - if:
+ properties:
+ compatible:
+@@ -89,6 +87,7 @@ examples:
+ reg = <0x1fa20000 0x400>,
+ <0x1fb00000 0x1000>;
+ #clock-cells = <1>;
++ #reset-cells = <1>;
+ };
+
+ - |
--- /dev/null
+From 0f7c637d1103d79829dec198e5f1b678c1feb5f2 Mon Sep 17 00:00:00 2001
+From: Lorenzo Bianconi <lorenzo@kernel.org>
+Date: Tue, 12 Nov 2024 01:08:48 +0100
+Subject: [PATCH] dt-bindings: clock: airoha: Update reg mapping for EN7581
+ SoC.
+
+clk-en7523 driver for EN7581 SoC is mapping all the scu memory region
+while it is configuring the chip-scu one via a syscon. Update the reg
+mapping definition for this device. This patch does not introduce any
+backward incompatibility since the dts for EN7581 SoC is not upstream
+yet.
+
+Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Link: https://lore.kernel.org/r/20241112-clk-en7581-syscon-v2-1-8ada5e394ae4@kernel.org
+Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ .../bindings/clock/airoha,en7523-scu.yaml | 23 +++++++------------
+ 1 file changed, 8 insertions(+), 15 deletions(-)
+
+--- a/Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml
++++ b/Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml
+@@ -34,8 +34,10 @@ properties:
+ - airoha,en7581-scu
+
+ reg:
+- minItems: 2
+- maxItems: 4
++ items:
++ - description: scu base address
++ - description: misc scu base address
++ minItems: 1
+
+ "#clock-cells":
+ description:
+@@ -60,9 +62,7 @@ allOf:
+ then:
+ properties:
+ reg:
+- items:
+- - description: scu base address
+- - description: misc scu base address
++ minItems: 2
+
+ - if:
+ properties:
+@@ -71,11 +71,7 @@ allOf:
+ then:
+ properties:
+ reg:
+- items:
+- - description: scu base address
+- - description: misc scu base address
+- - description: reset base address
+- - description: pb scu base address
++ maxItems: 1
+
+ additionalProperties: false
+
+@@ -95,12 +91,9 @@ examples:
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+- scuclk: clock-controller@1fa20000 {
++ scuclk: clock-controller@1fb00000 {
+ compatible = "airoha,en7581-scu";
+- reg = <0x0 0x1fa20000 0x0 0x400>,
+- <0x0 0x1fb00000 0x0 0x90>,
+- <0x0 0x1fb00830 0x0 0x8>,
+- <0x0 0x1fbe3400 0x0 0xfc>;
++ reg = <0x0 0x1fb00000 0x0 0x970>;
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ };
--- /dev/null
+From 947643509279a605a09959a06d332bf027e8be57 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Mon, 10 Nov 2025 06:56:43 +0300
+Subject: [PATCH] dt-bindings: clock: airoha: Add reset support to EN7523 clock
+ binding
+
+Introduce reset capability to EN7523 device-tree clock binding
+documentation.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ .../dt-bindings/reset/airoha,en7523-reset.h | 61 +++++++++++++++++++
+ 1 file changed, 61 insertions(+)
+ create mode 100644 include/dt-bindings/reset/airoha,en7523-reset.h
+
+--- /dev/null
++++ b/include/dt-bindings/reset/airoha,en7523-reset.h
+@@ -0,0 +1,61 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Copyright (C) 2024 iopsys Software Solutions AB.
++ * Copyright (C) 2025 Genexis AB.
++ *
++ * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
++ *
++ * based on
++ * include/dt-bindings/reset/airoha,en7581-reset.h
++ * by Lorenzo Bianconi <lorenzo@kernel.org>
++ */
++
++#ifndef __DT_BINDINGS_RESET_CONTROLLER_AIROHA_EN7523_H_
++#define __DT_BINDINGS_RESET_CONTROLLER_AIROHA_EN7523_H_
++
++/* RST_CTRL2 */
++#define EN7523_XPON_PHY_RST 0
++#define EN7523_XSI_MAC_RST 1
++#define EN7523_XSI_PHY_RST 2
++#define EN7523_NPU_RST 3
++#define EN7523_I2S_RST 4
++#define EN7523_TRNG_RST 5
++#define EN7523_TRNG_MSTART_RST 6
++#define EN7523_DUAL_HSI0_RST 7
++#define EN7523_DUAL_HSI1_RST 8
++#define EN7523_HSI_RST 9
++#define EN7523_DUAL_HSI0_MAC_RST 10
++#define EN7523_DUAL_HSI1_MAC_RST 11
++#define EN7523_HSI_MAC_RST 12
++#define EN7523_WDMA_RST 13
++#define EN7523_WOE0_RST 14
++#define EN7523_WOE1_RST 15
++#define EN7523_HSDMA_RST 16
++#define EN7523_I2C2RBUS_RST 17
++#define EN7523_TDMA_RST 18
++/* RST_CTRL1 */
++#define EN7523_PCM1_ZSI_ISI_RST 19
++#define EN7523_FE_PDMA_RST 20
++#define EN7523_FE_QDMA_RST 21
++#define EN7523_PCM_SPIWP_RST 22
++#define EN7523_CRYPTO_RST 23
++#define EN7523_TIMER_RST 24
++#define EN7523_PCM1_RST 25
++#define EN7523_UART_RST 26
++#define EN7523_GPIO_RST 27
++#define EN7523_GDMA_RST 28
++#define EN7523_I2C_MASTER_RST 29
++#define EN7523_PCM2_ZSI_ISI_RST 30
++#define EN7523_SFC_RST 31
++#define EN7523_UART2_RST 32
++#define EN7523_GDMP_RST 33
++#define EN7523_FE_RST 34
++#define EN7523_USB_HOST_P0_RST 35
++#define EN7523_GSW_RST 36
++#define EN7523_SFC2_PCM_RST 37
++#define EN7523_PCIE0_RST 38
++#define EN7523_PCIE1_RST 39
++#define EN7523_PCIE_HB_RST 40
++#define EN7523_XPON_MAC_RST 41
++
++#endif /* __DT_BINDINGS_RESET_CONTROLLER_AIROHA_EN7523_H_ */
--- /dev/null
+From 02d3b7557ce28c373ea1e925ae16ab5988284313 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Jan 2025 00:10:03 +0100
+Subject: [PATCH 1/2] dt-bindings: clock: drop NUM_CLOCKS define for EN7581
+
+Drop NUM_CLOCKS define for EN7581 include. This is not a binding and
+should not be placed here. Value is derived internally in the user
+driver.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Link: https://lore.kernel.org/r/20250113231030.6735-3-ansuelsmth@gmail.com
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ include/dt-bindings/clock/en7523-clk.h | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/include/dt-bindings/clock/en7523-clk.h
++++ b/include/dt-bindings/clock/en7523-clk.h
+@@ -12,6 +12,4 @@
+ #define EN7523_CLK_CRYPTO 6
+ #define EN7523_CLK_PCIE 7
+
+-#define EN7523_NUM_CLOCKS 8
+-
+ #endif /* _DT_BINDINGS_CLOCK_AIROHA_EN7523_H_ */
--- /dev/null
+From 82108ad3285f58f314ad41398f44017c7dbe44de Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Jan 2025 00:10:04 +0100
+Subject: [PATCH 2/2] dt-bindings: clock: add ID for eMMC for EN7581
+
+Add ID for eMMC for EN7581. This is to control clock selection of eMMC
+between 200MHz and 150MHz.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Acked-by: Conor Dooley <conor.dooley@microchip.com>
+Link: https://lore.kernel.org/r/20250113231030.6735-4-ansuelsmth@gmail.com
+Signed-off-by: Stephen Boyd <sboyd@kernel.org>
+---
+ include/dt-bindings/clock/en7523-clk.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/dt-bindings/clock/en7523-clk.h
++++ b/include/dt-bindings/clock/en7523-clk.h
+@@ -12,4 +12,6 @@
+ #define EN7523_CLK_CRYPTO 6
+ #define EN7523_CLK_PCIE 7
+
++#define EN7581_CLK_EMMC 8
++
+ #endif /* _DT_BINDINGS_CLOCK_AIROHA_EN7523_H_ */
--- /dev/null
+From 661856ca131c8bf6724905966e02149805660abe Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:16:53 +0300
+Subject: [PATCH 05/14] spi: airoha: remove unnecessary restriction length
+
+The "length < 160" restriction is not needed because airoha_snand_write_data()
+and airoha_snand_read_data() will properly handle data transfers above
+SPI_MAX_TRANSFER_SIZE.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251012121707.2296160-3-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 7 -------
+ 1 file changed, 7 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -619,13 +619,6 @@ static int airoha_snand_adjust_op_size(s
+
+ if (op->data.nbytes > max_len)
+ op->data.nbytes = max_len;
+- } else {
+- max_len = 1 + op->addr.nbytes + op->dummy.nbytes;
+- if (max_len >= 160)
+- return -EOPNOTSUPP;
+-
+- if (op->data.nbytes > 160 - max_len)
+- op->data.nbytes = 160 - max_len;
+ }
+
+ return 0;
--- /dev/null
+From 7350f8dc15bfbb7abf1ce4babea6fcace1c574c5 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:16:55 +0300
+Subject: [PATCH 06/14] spi: airoha: remove unnecessary switch to non-dma mode
+
+The code switches to dma at the start of dirmap operation and returns
+to non-dma at the end of dirmap operation, so an additional switch to
+non-dma at the start of dirmap write is not required.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251012121707.2296160-5-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -815,9 +815,6 @@ static ssize_t airoha_snand_dirmap_write
+ int err;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+- err = airoha_snand_set_mode(as_ctrl, SPI_MODE_MANUAL);
+- if (err < 0)
+- return err;
+
+ memcpy(txrx_buf + offs, buf, len);
+ dma_addr = dma_map_single(as_ctrl->dev, txrx_buf, SPI_NAND_CACHE_SIZE,
--- /dev/null
+From 233a22687411ea053a4b169c07324ee6aa33bf38 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:16:58 +0300
+Subject: [PATCH 07/14] spi: airoha: unify dirmap read/write code
+
+Makes dirmap writing looks similar to dirmap reading. Just a minor
+refactoring, no behavior change is expected.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Link: https://patch.msgid.link/20251012121707.2296160-8-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 50 ++++++++++++++++++++++-------------
+ 1 file changed, 32 insertions(+), 18 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -672,6 +672,8 @@ static ssize_t airoha_snand_dirmap_read(
+ u32 val, rd_mode;
+ int err;
+
++ as_ctrl = spi_controller_get_devdata(spi->controller);
++
+ switch (op->cmd.opcode) {
+ case SPI_NAND_OP_READ_FROM_CACHE_DUAL:
+ rd_mode = 1;
+@@ -684,7 +686,6 @@ static ssize_t airoha_snand_dirmap_read(
+ break;
+ }
+
+- as_ctrl = spi_controller_get_devdata(spi->controller);
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA);
+ if (err < 0)
+ return err;
+@@ -748,7 +749,7 @@ static ssize_t airoha_snand_dirmap_read(
+ if (err)
+ goto error_dma_unmap;
+
+- /* trigger dma start read */
++ /* trigger dma reading */
+ err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+ SPI_NFI_RD_TRIG);
+ if (err)
+@@ -806,37 +807,47 @@ error_dma_mode_off:
+ static ssize_t airoha_snand_dirmap_write(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, const void *buf)
+ {
+- struct spi_mem_op *op = &desc->info.op_tmpl;
+ struct spi_device *spi = desc->mem->spi;
+ u8 *txrx_buf = spi_get_ctldata(spi);
+ struct airoha_snand_ctrl *as_ctrl;
+ dma_addr_t dma_addr;
+- u32 wr_mode, val;
++ u32 wr_mode, val, opcode;
+ int err;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+
++ opcode = desc->info.op_tmpl.cmd.opcode;
++ switch (opcode) {
++ case SPI_NAND_OP_PROGRAM_LOAD_SINGLE:
++ case SPI_NAND_OP_PROGRAM_LOAD_RAMDOM_SINGLE:
++ wr_mode = 0;
++ break;
++ case SPI_NAND_OP_PROGRAM_LOAD_QUAD:
++ case SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD:
++ wr_mode = 2;
++ break;
++ default:
++ /* unknown opcode */
++ return -EOPNOTSUPP;
++ }
++
+ memcpy(txrx_buf + offs, buf, len);
+- dma_addr = dma_map_single(as_ctrl->dev, txrx_buf, SPI_NAND_CACHE_SIZE,
+- DMA_TO_DEVICE);
+- err = dma_mapping_error(as_ctrl->dev, dma_addr);
+- if (err)
+- return err;
+
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA);
+ if (err < 0)
+- goto error_dma_unmap;
++ return err;
+
+ err = airoha_snand_nfi_config(as_ctrl);
+ if (err)
+- goto error_dma_unmap;
++ goto error_dma_mode_off;
+
+- if (op->cmd.opcode == SPI_NAND_OP_PROGRAM_LOAD_QUAD ||
+- op->cmd.opcode == SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD)
+- wr_mode = BIT(1);
+- else
+- wr_mode = 0;
++ dma_addr = dma_map_single(as_ctrl->dev, txrx_buf, SPI_NAND_CACHE_SIZE,
++ DMA_TO_DEVICE);
++ err = dma_mapping_error(as_ctrl->dev, dma_addr);
++ if (err)
++ goto error_dma_mode_off;
+
++ /* set dma addr */
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_STRADDR,
+ dma_addr);
+ if (err)
+@@ -850,12 +861,13 @@ static ssize_t airoha_snand_dirmap_write
+ if (err)
+ goto error_dma_unmap;
+
++ /* set write command */
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_PG_CTL1,
+- FIELD_PREP(SPI_NFI_PG_LOAD_CMD,
+- op->cmd.opcode));
++ FIELD_PREP(SPI_NFI_PG_LOAD_CMD, opcode));
+ if (err)
+ goto error_dma_unmap;
+
++ /* set write mode */
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_SNF_MISC_CTL,
+ FIELD_PREP(SPI_NFI_DATA_READ_WR_MODE, wr_mode));
+ if (err)
+@@ -887,6 +899,7 @@ static ssize_t airoha_snand_dirmap_write
+ if (err)
+ goto error_dma_unmap;
+
++ /* trigger dma writing */
+ err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+ SPI_NFI_WR_TRIG);
+ if (err)
+@@ -931,6 +944,7 @@ static ssize_t airoha_snand_dirmap_write
+ error_dma_unmap:
+ dma_unmap_single(as_ctrl->dev, dma_addr, SPI_NAND_CACHE_SIZE,
+ DMA_TO_DEVICE);
++error_dma_mode_off:
+ airoha_snand_set_mode(as_ctrl, SPI_MODE_MANUAL);
+ return err;
+ }
--- /dev/null
+From 80b09137aeab27e59004383058f8cc696a9ee048 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:16:59 +0300
+Subject: [PATCH 08/14] spi: airoha: support of dualio/quadio flash reading
+ commands
+
+Airoha snfi spi controller supports acceleration of DUAL/QUAD
+operations, but does not supports DUAL_IO/QUAD_IO operations.
+Luckily DUAL/QUAD operations do the same as DUAL_IO/QUAD_IO ones,
+so we can issue corresponding DUAL/QUAD operation instead of
+DUAL_IO/QUAD_IO one.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251012121707.2296160-9-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 28 ++++++++++++++++++++++------
+ 1 file changed, 22 insertions(+), 6 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -147,6 +147,8 @@
+ #define SPI_NFI_CUS_SEC_SIZE_EN BIT(16)
+
+ #define REG_SPI_NFI_RD_CTL2 0x0510
++#define SPI_NFI_DATA_READ_CMD GENMASK(7, 0)
++
+ #define REG_SPI_NFI_RD_CTL3 0x0514
+
+ #define REG_SPI_NFI_PG_CTL1 0x0524
+@@ -179,7 +181,9 @@
+ #define SPI_NAND_OP_READ_FROM_CACHE_SINGLE 0x03
+ #define SPI_NAND_OP_READ_FROM_CACHE_SINGLE_FAST 0x0b
+ #define SPI_NAND_OP_READ_FROM_CACHE_DUAL 0x3b
++#define SPI_NAND_OP_READ_FROM_CACHE_DUALIO 0xbb
+ #define SPI_NAND_OP_READ_FROM_CACHE_QUAD 0x6b
++#define SPI_NAND_OP_READ_FROM_CACHE_QUADIO 0xeb
+ #define SPI_NAND_OP_WRITE_ENABLE 0x06
+ #define SPI_NAND_OP_WRITE_DISABLE 0x04
+ #define SPI_NAND_OP_PROGRAM_LOAD_SINGLE 0x02
+@@ -664,26 +668,38 @@ static int airoha_snand_dirmap_create(st
+ static ssize_t airoha_snand_dirmap_read(struct spi_mem_dirmap_desc *desc,
+ u64 offs, size_t len, void *buf)
+ {
+- struct spi_mem_op *op = &desc->info.op_tmpl;
+ struct spi_device *spi = desc->mem->spi;
+ struct airoha_snand_ctrl *as_ctrl;
+ u8 *txrx_buf = spi_get_ctldata(spi);
+ dma_addr_t dma_addr;
+- u32 val, rd_mode;
++ u32 val, rd_mode, opcode;
+ int err;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+
+- switch (op->cmd.opcode) {
++ /*
++ * DUALIO and QUADIO opcodes are not supported by the spi controller,
++ * replace them with supported opcodes.
++ */
++ opcode = desc->info.op_tmpl.cmd.opcode;
++ switch (opcode) {
++ case SPI_NAND_OP_READ_FROM_CACHE_SINGLE:
++ case SPI_NAND_OP_READ_FROM_CACHE_SINGLE_FAST:
++ rd_mode = 0;
++ break;
+ case SPI_NAND_OP_READ_FROM_CACHE_DUAL:
++ case SPI_NAND_OP_READ_FROM_CACHE_DUALIO:
++ opcode = SPI_NAND_OP_READ_FROM_CACHE_DUAL;
+ rd_mode = 1;
+ break;
+ case SPI_NAND_OP_READ_FROM_CACHE_QUAD:
++ case SPI_NAND_OP_READ_FROM_CACHE_QUADIO:
++ opcode = SPI_NAND_OP_READ_FROM_CACHE_QUAD;
+ rd_mode = 2;
+ break;
+ default:
+- rd_mode = 0;
+- break;
++ /* unknown opcode */
++ return -EOPNOTSUPP;
+ }
+
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA);
+@@ -717,7 +733,7 @@ static ssize_t airoha_snand_dirmap_read(
+
+ /* set read command */
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_RD_CTL2,
+- op->cmd.opcode);
++ FIELD_PREP(SPI_NFI_DATA_READ_CMD, opcode));
+ if (err)
+ goto error_dma_unmap;
+
--- /dev/null
+From 70eec454f2d6cdfab547c262781acd38328e11a1 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:17:00 +0300
+Subject: [PATCH 09/14] spi: airoha: avoid setting of page/oob sizes in
+ REG_SPI_NFI_PAGEFMT
+
+spi-airoha-snfi uses custom sector size in REG_SPI_NFI_SECCUS_SIZE
+register, so setting of page/oob sizes in REG_SPI_NFI_PAGEFMT is not
+required.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Link: https://patch.msgid.link/20251012121707.2296160-10-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 38 -----------------------------------
+ 1 file changed, 38 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -518,44 +518,6 @@ static int airoha_snand_nfi_config(struc
+ if (err)
+ return err;
+
+- /* page format */
+- switch (as_ctrl->nfi_cfg.spare_size) {
+- case 26:
+- val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x1);
+- break;
+- case 27:
+- val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x2);
+- break;
+- case 28:
+- val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x3);
+- break;
+- default:
+- val = FIELD_PREP(SPI_NFI_SPARE_SIZE, 0x0);
+- break;
+- }
+-
+- err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_PAGEFMT,
+- SPI_NFI_SPARE_SIZE, val);
+- if (err)
+- return err;
+-
+- switch (as_ctrl->nfi_cfg.page_size) {
+- case 2048:
+- val = FIELD_PREP(SPI_NFI_PAGE_SIZE, 0x1);
+- break;
+- case 4096:
+- val = FIELD_PREP(SPI_NFI_PAGE_SIZE, 0x2);
+- break;
+- default:
+- val = FIELD_PREP(SPI_NFI_PAGE_SIZE, 0x0);
+- break;
+- }
+-
+- err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_PAGEFMT,
+- SPI_NFI_PAGE_SIZE, val);
+- if (err)
+- return err;
+-
+ /* sec num */
+ val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num);
+ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
--- /dev/null
+From d1ff30df1d9a4eb4c067795abb5e2a66910fd108 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:17:01 +0300
+Subject: [PATCH 10/14] spi: airoha: reduce the number of modification of
+ REG_SPI_NFI_CNFG and REG_SPI_NFI_SECCUS_SIZE registers
+
+This just reduce the number of modification of REG_SPI_NFI_CNFG and
+REG_SPI_NFI_SECCUS_SIZE registers during dirmap operation.
+
+This patch is a necessary step to avoid reading flash page settings
+from SNFI registers during driver startup.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251012121707.2296160-11-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 135 +++++++++++++++++++++++++---------
+ 1 file changed, 102 insertions(+), 33 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -668,7 +668,48 @@ static ssize_t airoha_snand_dirmap_read(
+ if (err < 0)
+ return err;
+
+- err = airoha_snand_nfi_config(as_ctrl);
++ /* NFI reset */
++ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
++ SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
++ if (err)
++ goto error_dma_mode_off;
++
++ /* NFI configure:
++ * - No AutoFDM (custom sector size (SECCUS) register will be used)
++ * - No SoC's hardware ECC (flash internal ECC will be used)
++ * - Use burst mode (faster, but requires 16 byte alignment for addresses)
++ * - Setup for reading (SPI_NFI_READ_MODE)
++ * - Setup reading command: FIELD_PREP(SPI_NFI_OPMODE, 6)
++ * - Use DMA instead of PIO for data reading
++ */
++ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
++ SPI_NFI_DMA_MODE |
++ SPI_NFI_READ_MODE |
++ SPI_NFI_DMA_BURST_EN |
++ SPI_NFI_HW_ECC_EN |
++ SPI_NFI_AUTO_FDM_EN |
++ SPI_NFI_OPMODE,
++ SPI_NFI_DMA_MODE |
++ SPI_NFI_READ_MODE |
++ SPI_NFI_DMA_BURST_EN |
++ FIELD_PREP(SPI_NFI_OPMODE, 6));
++ if (err)
++ goto error_dma_mode_off;
++
++ /* Set number of sector will be read */
++ val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num);
++ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
++ SPI_NFI_SEC_NUM, val);
++ if (err)
++ goto error_dma_mode_off;
++
++ /* Set custom sector size */
++ val = as_ctrl->nfi_cfg.sec_size;
++ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE,
++ SPI_NFI_CUS_SEC_SIZE |
++ SPI_NFI_CUS_SEC_SIZE_EN,
++ FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) |
++ SPI_NFI_CUS_SEC_SIZE_EN);
+ if (err)
+ goto error_dma_mode_off;
+
+@@ -684,7 +725,14 @@ static ssize_t airoha_snand_dirmap_read(
+ if (err)
+ goto error_dma_unmap;
+
+- /* set cust sec size */
++ /*
++ * Setup transfer length
++ * ---------------------
++ * The following rule MUST be met:
++ * transfer_length =
++ * = NFI_SNF_MISC_CTL2.read_data_byte_number =
++ * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size
++ */
+ val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num;
+ val = FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, val);
+ err = regmap_update_bits(as_ctrl->regmap_nfi,
+@@ -711,18 +759,6 @@ static ssize_t airoha_snand_dirmap_read(
+ if (err)
+ goto error_dma_unmap;
+
+- /* set nfi read */
+- err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_OPMODE,
+- FIELD_PREP(SPI_NFI_OPMODE, 6));
+- if (err)
+- goto error_dma_unmap;
+-
+- err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_READ_MODE | SPI_NFI_DMA_MODE);
+- if (err)
+- goto error_dma_unmap;
+-
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CMD, 0x0);
+ if (err)
+ goto error_dma_unmap;
+@@ -815,7 +851,48 @@ static ssize_t airoha_snand_dirmap_write
+ if (err < 0)
+ return err;
+
+- err = airoha_snand_nfi_config(as_ctrl);
++ /* NFI reset */
++ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
++ SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
++ if (err)
++ goto error_dma_mode_off;
++
++ /*
++ * NFI configure:
++ * - No AutoFDM (custom sector size (SECCUS) register will be used)
++ * - No SoC's hardware ECC (flash internal ECC will be used)
++ * - Use burst mode (faster, but requires 16 byte alignment for addresses)
++ * - Setup for writing (SPI_NFI_READ_MODE bit is cleared)
++ * - Setup writing command: FIELD_PREP(SPI_NFI_OPMODE, 3)
++ * - Use DMA instead of PIO for data writing
++ */
++ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
++ SPI_NFI_DMA_MODE |
++ SPI_NFI_READ_MODE |
++ SPI_NFI_DMA_BURST_EN |
++ SPI_NFI_HW_ECC_EN |
++ SPI_NFI_AUTO_FDM_EN |
++ SPI_NFI_OPMODE,
++ SPI_NFI_DMA_MODE |
++ SPI_NFI_DMA_BURST_EN |
++ FIELD_PREP(SPI_NFI_OPMODE, 3));
++ if (err)
++ goto error_dma_mode_off;
++
++ /* Set number of sector will be written */
++ val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num);
++ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
++ SPI_NFI_SEC_NUM, val);
++ if (err)
++ goto error_dma_mode_off;
++
++ /* Set custom sector size */
++ val = as_ctrl->nfi_cfg.sec_size;
++ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE,
++ SPI_NFI_CUS_SEC_SIZE |
++ SPI_NFI_CUS_SEC_SIZE_EN,
++ FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) |
++ SPI_NFI_CUS_SEC_SIZE_EN);
+ if (err)
+ goto error_dma_mode_off;
+
+@@ -831,8 +908,16 @@ static ssize_t airoha_snand_dirmap_write
+ if (err)
+ goto error_dma_unmap;
+
+- val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM,
+- as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num);
++ /*
++ * Setup transfer length
++ * ---------------------
++ * The following rule MUST be met:
++ * transfer_length =
++ * = NFI_SNF_MISC_CTL2.write_data_byte_number =
++ * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size
++ */
++ val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num;
++ val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, val);
+ err = regmap_update_bits(as_ctrl->regmap_nfi,
+ REG_SPI_NFI_SNF_MISC_CTL2,
+ SPI_NFI_PROG_LOAD_BYTE_NUM, val);
+@@ -857,22 +942,6 @@ static ssize_t airoha_snand_dirmap_write
+ if (err)
+ goto error_dma_unmap;
+
+- err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_READ_MODE);
+- if (err)
+- goto error_dma_unmap;
+-
+- err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_OPMODE,
+- FIELD_PREP(SPI_NFI_OPMODE, 3));
+- if (err)
+- goto error_dma_unmap;
+-
+- err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_DMA_MODE);
+- if (err)
+- goto error_dma_unmap;
+-
+ err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CMD, 0x80);
+ if (err)
+ goto error_dma_unmap;
--- /dev/null
+From fb81b5cecb8553e3ca2b45288cf340d43c9c2991 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:17:02 +0300
+Subject: [PATCH 11/14] spi: airoha: set custom sector size equal to flash page
+ size
+
+Set custom sector size equal to flash page size including oob. Thus we
+will always read a single sector. The maximum custom sector size is
+8187, so all possible flash sector sizes are supported.
+
+This patch is a necessary step to avoid reading flash page settings
+from SNFI registers during driver startup.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251012121707.2296160-12-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 35 +++++++++++++++++++----------------
+ 1 file changed, 19 insertions(+), 16 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -519,7 +519,7 @@ static int airoha_snand_nfi_config(struc
+ return err;
+
+ /* sec num */
+- val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num);
++ val = FIELD_PREP(SPI_NFI_SEC_NUM, 1);
+ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+ SPI_NFI_SEC_NUM, val);
+ if (err)
+@@ -532,7 +532,8 @@ static int airoha_snand_nfi_config(struc
+ return err;
+
+ /* set cust sec size */
+- val = FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, as_ctrl->nfi_cfg.sec_size);
++ val = FIELD_PREP(SPI_NFI_CUS_SEC_SIZE,
++ as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num);
+ return regmap_update_bits(as_ctrl->regmap_nfi,
+ REG_SPI_NFI_SECCUS_SIZE,
+ SPI_NFI_CUS_SEC_SIZE, val);
+@@ -635,10 +636,13 @@ static ssize_t airoha_snand_dirmap_read(
+ u8 *txrx_buf = spi_get_ctldata(spi);
+ dma_addr_t dma_addr;
+ u32 val, rd_mode, opcode;
++ size_t bytes;
+ int err;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+
++ bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size;
++
+ /*
+ * DUALIO and QUADIO opcodes are not supported by the spi controller,
+ * replace them with supported opcodes.
+@@ -697,18 +701,17 @@ static ssize_t airoha_snand_dirmap_read(
+ goto error_dma_mode_off;
+
+ /* Set number of sector will be read */
+- val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num);
+ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+- SPI_NFI_SEC_NUM, val);
++ SPI_NFI_SEC_NUM,
++ FIELD_PREP(SPI_NFI_SEC_NUM, 1));
+ if (err)
+ goto error_dma_mode_off;
+
+ /* Set custom sector size */
+- val = as_ctrl->nfi_cfg.sec_size;
+ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE,
+ SPI_NFI_CUS_SEC_SIZE |
+ SPI_NFI_CUS_SEC_SIZE_EN,
+- FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) |
++ FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, bytes) |
+ SPI_NFI_CUS_SEC_SIZE_EN);
+ if (err)
+ goto error_dma_mode_off;
+@@ -733,11 +736,10 @@ static ssize_t airoha_snand_dirmap_read(
+ * = NFI_SNF_MISC_CTL2.read_data_byte_number =
+ * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size
+ */
+- val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num;
+- val = FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, val);
+ err = regmap_update_bits(as_ctrl->regmap_nfi,
+ REG_SPI_NFI_SNF_MISC_CTL2,
+- SPI_NFI_READ_DATA_BYTE_NUM, val);
++ SPI_NFI_READ_DATA_BYTE_NUM,
++ FIELD_PREP(SPI_NFI_READ_DATA_BYTE_NUM, bytes));
+ if (err)
+ goto error_dma_unmap;
+
+@@ -826,10 +828,13 @@ static ssize_t airoha_snand_dirmap_write
+ struct airoha_snand_ctrl *as_ctrl;
+ dma_addr_t dma_addr;
+ u32 wr_mode, val, opcode;
++ size_t bytes;
+ int err;
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+
++ bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size;
++
+ opcode = desc->info.op_tmpl.cmd.opcode;
+ switch (opcode) {
+ case SPI_NAND_OP_PROGRAM_LOAD_SINGLE:
+@@ -880,18 +885,17 @@ static ssize_t airoha_snand_dirmap_write
+ goto error_dma_mode_off;
+
+ /* Set number of sector will be written */
+- val = FIELD_PREP(SPI_NFI_SEC_NUM, as_ctrl->nfi_cfg.sec_num);
+ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+- SPI_NFI_SEC_NUM, val);
++ SPI_NFI_SEC_NUM,
++ FIELD_PREP(SPI_NFI_SEC_NUM, 1));
+ if (err)
+ goto error_dma_mode_off;
+
+ /* Set custom sector size */
+- val = as_ctrl->nfi_cfg.sec_size;
+ err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE,
+ SPI_NFI_CUS_SEC_SIZE |
+ SPI_NFI_CUS_SEC_SIZE_EN,
+- FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, val) |
++ FIELD_PREP(SPI_NFI_CUS_SEC_SIZE, bytes) |
+ SPI_NFI_CUS_SEC_SIZE_EN);
+ if (err)
+ goto error_dma_mode_off;
+@@ -916,11 +920,10 @@ static ssize_t airoha_snand_dirmap_write
+ * = NFI_SNF_MISC_CTL2.write_data_byte_number =
+ * = NFI_CON.sector_number * NFI_SECCUS.custom_sector_size
+ */
+- val = as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num;
+- val = FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, val);
+ err = regmap_update_bits(as_ctrl->regmap_nfi,
+ REG_SPI_NFI_SNF_MISC_CTL2,
+- SPI_NFI_PROG_LOAD_BYTE_NUM, val);
++ SPI_NFI_PROG_LOAD_BYTE_NUM,
++ FIELD_PREP(SPI_NFI_PROG_LOAD_BYTE_NUM, bytes));
+ if (err)
+ goto error_dma_unmap;
+
--- /dev/null
+From 902c0ea18a97b1a6eeee5799cb1fd9a79ef9208e Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:17:03 +0300
+Subject: [PATCH 12/14] spi: airoha: avoid reading flash page settings from
+ SNFI registers during driver startup
+
+The spinand driver do 3 type of dirmap requests:
+ * read/write whole flash page without oob
+ (offs = 0, len = page_size)
+ * read/write whole flash page including oob
+ (offs = 0, len = page_size + oob_size)
+ * read/write oob area only
+ (offs = page_size, len = oob_size)
+
+The trick is:
+ * read/write a single "sector"
+ * set a custom sector size equal to offs + len. It's a bit safer to
+ rounded up "sector size" value 64.
+ * set the transfer length equal to custom sector size
+
+And it works!
+
+Thus we can remove a dirty hack that reads flash page settings from
+SNFI registers during driver startup. Also airoha_snand_adjust_op_size()
+function becomes unnecessary.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Link: https://patch.msgid.link/20251012121707.2296160-13-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 115 ++--------------------------------
+ 1 file changed, 5 insertions(+), 110 deletions(-)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -223,13 +223,6 @@ struct airoha_snand_ctrl {
+ struct regmap *regmap_ctrl;
+ struct regmap *regmap_nfi;
+ struct clk *spi_clk;
+-
+- struct {
+- size_t page_size;
+- size_t sec_size;
+- u8 sec_num;
+- u8 spare_size;
+- } nfi_cfg;
+ };
+
+ static int airoha_snand_set_fifo_op(struct airoha_snand_ctrl *as_ctrl,
+@@ -490,55 +483,6 @@ static int airoha_snand_nfi_init(struct
+ SPI_NFI_ALL_IRQ_EN, SPI_NFI_AHB_DONE_EN);
+ }
+
+-static int airoha_snand_nfi_config(struct airoha_snand_ctrl *as_ctrl)
+-{
+- int err;
+- u32 val;
+-
+- err = regmap_write(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+- SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+- if (err)
+- return err;
+-
+- /* auto FDM */
+- err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_AUTO_FDM_EN);
+- if (err)
+- return err;
+-
+- /* HW ECC */
+- err = regmap_clear_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_HW_ECC_EN);
+- if (err)
+- return err;
+-
+- /* DMA Burst */
+- err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CNFG,
+- SPI_NFI_DMA_BURST_EN);
+- if (err)
+- return err;
+-
+- /* sec num */
+- val = FIELD_PREP(SPI_NFI_SEC_NUM, 1);
+- err = regmap_update_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_CON,
+- SPI_NFI_SEC_NUM, val);
+- if (err)
+- return err;
+-
+- /* enable cust sec size */
+- err = regmap_set_bits(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE,
+- SPI_NFI_CUS_SEC_SIZE_EN);
+- if (err)
+- return err;
+-
+- /* set cust sec size */
+- val = FIELD_PREP(SPI_NFI_CUS_SEC_SIZE,
+- as_ctrl->nfi_cfg.sec_size * as_ctrl->nfi_cfg.sec_num);
+- return regmap_update_bits(as_ctrl->regmap_nfi,
+- REG_SPI_NFI_SECCUS_SIZE,
+- SPI_NFI_CUS_SEC_SIZE, val);
+-}
+-
+ static bool airoha_snand_is_page_ops(const struct spi_mem_op *op)
+ {
+ if (op->addr.nbytes != 2)
+@@ -571,26 +515,6 @@ static bool airoha_snand_is_page_ops(con
+ }
+ }
+
+-static int airoha_snand_adjust_op_size(struct spi_mem *mem,
+- struct spi_mem_op *op)
+-{
+- size_t max_len;
+-
+- if (airoha_snand_is_page_ops(op)) {
+- struct airoha_snand_ctrl *as_ctrl;
+-
+- as_ctrl = spi_controller_get_devdata(mem->spi->controller);
+- max_len = as_ctrl->nfi_cfg.sec_size;
+- max_len += as_ctrl->nfi_cfg.spare_size;
+- max_len *= as_ctrl->nfi_cfg.sec_num;
+-
+- if (op->data.nbytes > max_len)
+- op->data.nbytes = max_len;
+- }
+-
+- return 0;
+-}
+-
+ static bool airoha_snand_supports_op(struct spi_mem *mem,
+ const struct spi_mem_op *op)
+ {
+@@ -641,7 +565,8 @@ static ssize_t airoha_snand_dirmap_read(
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+
+- bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size;
++ /* minimum oob size is 64 */
++ bytes = round_up(offs + len, 64);
+
+ /*
+ * DUALIO and QUADIO opcodes are not supported by the spi controller,
+@@ -833,7 +758,8 @@ static ssize_t airoha_snand_dirmap_write
+
+ as_ctrl = spi_controller_get_devdata(spi->controller);
+
+- bytes = as_ctrl->nfi_cfg.sec_num * as_ctrl->nfi_cfg.sec_size;
++ /* minimum oob size is 64 */
++ bytes = round_up(offs + len, 64);
+
+ opcode = desc->info.op_tmpl.cmd.opcode;
+ switch (opcode) {
+@@ -1076,7 +1002,6 @@ static int airoha_snand_exec_op(struct s
+ }
+
+ static const struct spi_controller_mem_ops airoha_snand_mem_ops = {
+- .adjust_op_size = airoha_snand_adjust_op_size,
+ .supports_op = airoha_snand_supports_op,
+ .exec_op = airoha_snand_exec_op,
+ .dirmap_create = airoha_snand_dirmap_create,
+@@ -1106,36 +1031,6 @@ static int airoha_snand_setup(struct spi
+ return 0;
+ }
+
+-static int airoha_snand_nfi_setup(struct airoha_snand_ctrl *as_ctrl)
+-{
+- u32 val, sec_size, sec_num;
+- int err;
+-
+- err = regmap_read(as_ctrl->regmap_nfi, REG_SPI_NFI_CON, &val);
+- if (err)
+- return err;
+-
+- sec_num = FIELD_GET(SPI_NFI_SEC_NUM, val);
+-
+- err = regmap_read(as_ctrl->regmap_nfi, REG_SPI_NFI_SECCUS_SIZE, &val);
+- if (err)
+- return err;
+-
+- sec_size = FIELD_GET(SPI_NFI_CUS_SEC_SIZE, val);
+-
+- /* init default value */
+- as_ctrl->nfi_cfg.sec_size = sec_size;
+- as_ctrl->nfi_cfg.sec_num = sec_num;
+- as_ctrl->nfi_cfg.page_size = round_down(sec_size * sec_num, 1024);
+- as_ctrl->nfi_cfg.spare_size = 16;
+-
+- err = airoha_snand_nfi_init(as_ctrl);
+- if (err)
+- return err;
+-
+- return airoha_snand_nfi_config(as_ctrl);
+-}
+-
+ static const struct regmap_config spi_ctrl_regmap_config = {
+ .name = "ctrl",
+ .reg_bits = 32,
+@@ -1227,7 +1122,7 @@ static int airoha_snand_probe(struct pla
+ ctrl->setup = airoha_snand_setup;
+ device_set_node(&ctrl->dev, dev_fwnode(dev));
+
+- err = airoha_snand_nfi_setup(as_ctrl);
++ err = airoha_snand_nfi_init(as_ctrl);
+ if (err)
+ return err;
+
--- /dev/null
+From 0743acf746a81e0460a56fd5ff847d97fa7eb370 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Date: Sun, 12 Oct 2025 15:17:04 +0300
+Subject: [PATCH 13/14] spi: airoha: buffer must be 0xff-ed before writing
+
+During writing, the entire flash page (including OOB) will be updated
+with the values from the temporary buffer, so we need to fill the
+untouched areas of the buffer with 0xff value to prevent accidental
+data overwriting.
+
+Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+Link: https://patch.msgid.link/20251012121707.2296160-14-mikhail.kshevetskiy@iopsys.eu
+Signed-off-by: Mark Brown <broonie@kernel.org>
+---
+ drivers/spi/spi-airoha-snfi.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/spi/spi-airoha-snfi.c
++++ b/drivers/spi/spi-airoha-snfi.c
+@@ -776,7 +776,11 @@ static ssize_t airoha_snand_dirmap_write
+ return -EOPNOTSUPP;
+ }
+
++ if (offs > 0)
++ memset(txrx_buf, 0xff, offs);
+ memcpy(txrx_buf + offs, buf, len);
++ if (bytes > offs + len)
++ memset(txrx_buf + offs + len, 0xff, bytes - offs - len);
+
+ err = airoha_snand_set_mode(as_ctrl, SPI_MODE_DMA);
+ if (err < 0)
--- /dev/null
+From: Ahmed Naseef <naseefkm@gmail.com>
+Subject: mips: econet: add EN7528 SoC support
+
+The EN7528 is a little endian dual-core MIPS 1004Kc SoC used in xPON
+devices. Unlike the big endian EN751221, EN7528 uses the MIPS GIC
+interrupt controller for SMP.
+
+This adds boot support for the EN7528 SoC family:
+- New SOC_ECONET_EN7528 Kconfig option
+- Little endian support for ECONET platform
+- UART base address adjustment for endianness
+- CPS SMP ops registration for multi-core support
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -391,13 +391,13 @@ config MACH_DECSTATION
+ config ECONET
+ bool "EcoNet MIPS family"
+ select BOOT_RAW
+- select CPU_BIG_ENDIAN
+ select DEBUG_ZBOOT if DEBUG_KERNEL
+ select EARLY_PRINTK_8250
+ select ECONET_EN751221_TIMER
+ select SERIAL_8250
+ select SERIAL_OF_PLATFORM
+ select SYS_SUPPORTS_BIG_ENDIAN
++ select SYS_SUPPORTS_LITTLE_ENDIAN
+ select SYS_HAS_CPU_MIPS32_R1
+ select SYS_HAS_CPU_MIPS32_R2
+ select SYS_HAS_EARLY_PRINTK
+--- a/arch/mips/econet/Kconfig
++++ b/arch/mips/econet/Kconfig
+@@ -12,6 +12,7 @@ choice
+ config SOC_ECONET_EN751221
+ bool "EN751221 family"
+ select COMMON_CLK
++ select CPU_BIG_ENDIAN
+ select ECONET_EN751221_INTC
+ select IRQ_MIPS_CPU
+ select SMP
+@@ -22,6 +23,23 @@ choice
+ They are based on single core MIPS 34Kc processors. To boot
+ this kernel, you will need a device tree such as
+ MIPS_RAW_APPENDED_DTB=y, and a root filesystem.
++
++ config SOC_ECONET_EN7528
++ bool "EN7528 family"
++ select COMMON_CLK
++ select CPU_LITTLE_ENDIAN
++ select IRQ_MIPS_CPU
++ select MIPS_CPU_SCACHE
++ select MIPS_GIC
++ select SMP
++ select SMP_UP
++ select SYS_SUPPORTS_HIGHMEM
++ select SYS_SUPPORTS_MIPS_CPS
++ select SYS_SUPPORTS_MULTITHREADING
++ select SYS_SUPPORTS_SMP
++ help
++ The EN7528 family with dual-core MIPS 1004Kc.
++ Requires MIPS_RAW_APPENDED_DTB=y for boot.
+ endchoice
+
+ choice
+--- a/arch/mips/econet/init.c
++++ b/arch/mips/econet/init.c
+@@ -16,11 +16,16 @@
+ #include <asm/prom.h>
+ #include <asm/smp-ops.h>
+ #include <asm/reboot.h>
++#include <asm/mips-cps.h>
+
+ #define CR_AHB_RSTCR ((void __iomem *)CKSEG1ADDR(0x1fb00040))
+ #define RESET BIT(31)
+
+-#define UART_BASE CKSEG1ADDR(0x1fbf0003)
++#ifdef CONFIG_CPU_LITTLE_ENDIAN
++#define UART_BASE CKSEG1ADDR(0x1fbf0000) /* LE: byte at offset 0 */
++#else
++#define UART_BASE CKSEG1ADDR(0x1fbf0003) /* BE: byte at offset 3 */
++#endif
+ #define UART_REG_SHIFT 2
+
+ static void hw_reset(char *command)
+@@ -51,11 +56,18 @@ void __init plat_mem_setup(void)
+ early_init_dt_scan_memory();
+ }
+
+-/* 3. Overload __weak device_tree_init(), add SMP_UP ops */
++/* 3. Overload __weak device_tree_init(), register SMP ops */
+ void __init device_tree_init(void)
+ {
+ unflatten_and_copy_device_tree();
+
++ /* EN7528 dual-core: probe CM/CPC and register CPS SMP ops */
++ mips_cm_probe();
++ mips_cpc_probe();
++
++ if (!register_cps_smp_ops())
++ return;
++
+ register_up_smp_ops();
+ }
+
+--- a/arch/mips/boot/compressed/uart-16550.c
++++ b/arch/mips/boot/compressed/uart-16550.c
+@@ -21,7 +21,11 @@
+ #endif
+
+ #ifdef CONFIG_ECONET
++#ifdef CONFIG_CPU_LITTLE_ENDIAN
++#define EN75_UART_BASE 0x1fbf0000
++#else
+ #define EN75_UART_BASE 0x1fbf0003
++#endif
+ #define PORT(offset) (CKSEG1ADDR(EN75_UART_BASE) + (4 * (offset)))
+ #endif
+
--- /dev/null
+From: Ahmed Naseef <naseefkm@gmail.com>
+Subject: mips: econet: timer: add EN7528 support to EN751221 timer driver
+
+Extend the existing EN751221 timer driver to support EN7528/EN751627 SoCs.
+The driver now auto-detects the IRQ mode based on device tree:
+- EN751221: Single percpu IRQ (legacy interrupt controller)
+- EN7528/EN751627: Separate IRQ per CPU (GIC shared interrupts)
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/clocksource/Kconfig
++++ b/drivers/clocksource/Kconfig
+@@ -79,7 +79,10 @@ config ECONET_EN751221_TIMER
+ select CLKSRC_MMIO
+ select TIMER_OF
+ help
+- Support for CPU timer found on EcoNet MIPS based SoCs.
++ Support for CPU timer found on EcoNet EN75xx MIPS based SoCs
++ (EN751221, EN751627, EN7528). The driver supports both GIC-based
++ (separate IRQ per CPU) and legacy interrupt controller (percpu IRQ)
++ modes.
+
+ config FTTMR010_TIMER
+ bool "Faraday Technology timer driver" if COMPILE_TEST
+--- a/drivers/clocksource/timer-econet-en751221.c
++++ b/drivers/clocksource/timer-econet-en751221.c
+@@ -2,12 +2,20 @@
+ /*
+ * Timer present on EcoNet EN75xx MIPS based SoCs.
+ *
++ * This driver supports both:
++ * - EN751221: Single percpu IRQ mode (legacy interrupt controller)
++ * - EN7528/EN751627: Separate IRQ per CPU mode (GIC shared interrupts)
++ *
++ * The mode is auto-detected based on IRQ count in device tree.
++ *
+ * Copyright (C) 2025 by Caleb James DeLisle <cjd@cjdns.fr>
++ * Copyright (C) 2025 by Ahmed Naseef <naseefkm@gmail.com>
+ */
+
+ #include <linux/io.h>
+ #include <linux/cpumask.h>
+ #include <linux/interrupt.h>
++#include <linux/irq.h>
+ #include <linux/clockchips.h>
+ #include <linux/sched_clock.h>
+ #include <linux/of.h>
+@@ -21,10 +29,14 @@
+ #define ECONET_MAX_DELTA GENMASK(ECONET_BITS - 2, 0)
+ /* 34Kc hardware has 1 block and 1004Kc has 2. */
+ #define ECONET_NUM_BLOCKS DIV_ROUND_UP(NR_CPUS, 2)
++#define ECONET_MAX_IRQS 4
+
+ static struct {
+ void __iomem *membase[ECONET_NUM_BLOCKS];
+ u32 freq_hz;
++ int irqs[ECONET_MAX_IRQS];
++ int num_irqs;
++ bool use_percpu_irq;
+ } econet_timer __ro_after_init;
+
+ static DEFINE_PER_CPU(struct clock_event_device, econet_timer_pcpu);
+@@ -98,12 +110,21 @@ static int cevt_init_cpu(uint cpu)
+ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
+ u32 reg;
+
++ if (!econet_timer.use_percpu_irq && cpu >= econet_timer.num_irqs)
++ return -EINVAL;
++
+ pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
+
+ reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
+ iowrite32(reg, reg_ctl(cpu));
+
+- enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
++ if (econet_timer.use_percpu_irq) {
++ enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
++ } else {
++ if (irq_force_affinity(econet_timer.irqs[cpu], cpumask_of(cpu)))
++ pr_warn("%s: failed to set IRQ %d affinity to CPU %d\n",
++ cd->name, econet_timer.irqs[cpu], cpu);
++ }
+
+ /* Do this last because it synchronously configures the timer */
+ clockevents_config_and_register(cd, econet_timer.freq_hz,
+@@ -126,7 +147,21 @@ static void __init cevt_dev_init(uint cp
+ iowrite32(U32_MAX, reg_compare(cpu));
+ }
+
+-static int __init cevt_init(struct device_node *np)
++static void __init cevt_setup_clockevent(struct clock_event_device *cd,
++ struct device_node *np,
++ int irq, int cpu)
++{
++ cd->rating = 310;
++ cd->features = CLOCK_EVT_FEAT_ONESHOT |
++ CLOCK_EVT_FEAT_C3STOP |
++ CLOCK_EVT_FEAT_PERCPU;
++ cd->set_next_event = cevt_set_next_event;
++ cd->irq = irq;
++ cd->cpumask = cpumask_of(cpu);
++ cd->name = np->name;
++}
++
++static int __init cevt_init_percpu(struct device_node *np)
+ {
+ int i, irq, ret;
+
+@@ -137,42 +172,85 @@ static int __init cevt_init(struct devic
+ }
+
+ ret = request_percpu_irq(irq, cevt_interrupt, np->name, &econet_timer_pcpu);
+-
+ if (ret < 0) {
+ pr_err("%pOFn: IRQ %d setup failed (%d)\n", np, irq, ret);
+- goto err_unmap_irq;
++ irq_dispose_mapping(irq);
++ return ret;
+ }
+
+ for_each_possible_cpu(i) {
+ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
+
+- cd->rating = 310,
+- cd->features = CLOCK_EVT_FEAT_ONESHOT |
+- CLOCK_EVT_FEAT_C3STOP |
+- CLOCK_EVT_FEAT_PERCPU;
+- cd->set_next_event = cevt_set_next_event;
+- cd->irq = irq;
+- cd->cpumask = cpumask_of(i);
+- cd->name = np->name;
++ cevt_setup_clockevent(cd, np, irq, i);
++ cevt_dev_init(i);
++ }
++
++ return 0;
++}
++
++static int __init cevt_init_separate(struct device_node *np)
++{
++ int i, ret;
++
++ for (i = 0; i < econet_timer.num_irqs; i++) {
++ struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
++
++ econet_timer.irqs[i] = irq_of_parse_and_map(np, i);
++ if (econet_timer.irqs[i] <= 0) {
++ pr_err("%pOFn: irq_of_parse_and_map failed", np);
++ ret = -EINVAL;
++ goto err_free_irqs;
++ }
++
++ ret = request_irq(econet_timer.irqs[i], cevt_interrupt,
++ IRQF_TIMER | IRQF_NOBALANCING,
++ np->name, NULL);
++ if (ret < 0) {
++ pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
++ econet_timer.irqs[i], ret);
++ irq_dispose_mapping(econet_timer.irqs[i]);
++ goto err_free_irqs;
++ }
+
++ cevt_setup_clockevent(cd, np, econet_timer.irqs[i], i);
+ cevt_dev_init(i);
+ }
+
+- cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+- "clockevents/econet/timer:starting",
+- cevt_init_cpu, NULL);
+ return 0;
+
+-err_unmap_irq:
+- irq_dispose_mapping(irq);
++err_free_irqs:
++ while (--i >= 0) {
++ free_irq(econet_timer.irqs[i], NULL);
++ irq_dispose_mapping(econet_timer.irqs[i]);
++ }
+ return ret;
+ }
+
++static int __init cevt_init(struct device_node *np)
++{
++ econet_timer.num_irqs = of_irq_count(np);
++ if (econet_timer.num_irqs <= 0 || econet_timer.num_irqs > ECONET_MAX_IRQS) {
++ pr_err("%pOFn: invalid IRQ count %d\n", np, econet_timer.num_irqs);
++ return -EINVAL;
++ }
++
++ /* Auto-detect mode based on IRQ count:
++ * 1 IRQ = percpu mode (EN751221)
++ * N IRQs = separate IRQ per CPU (EN7528/EN751627)
++ */
++ econet_timer.use_percpu_irq = (econet_timer.num_irqs == 1);
++
++ if (econet_timer.use_percpu_irq)
++ return cevt_init_percpu(np);
++ else
++ return cevt_init_separate(np);
++}
++
+ static int __init timer_init(struct device_node *np)
+ {
+ int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
+ struct clk *clk;
+- int ret;
++ int ret, i;
+
+ clk = of_clk_get(np, 0);
+ if (IS_ERR(clk)) {
+@@ -182,11 +260,12 @@ static int __init timer_init(struct devi
+
+ econet_timer.freq_hz = clk_get_rate(clk);
+
+- for (int i = 0; i < num_blocks; i++) {
++ for (i = 0; i < num_blocks; i++) {
+ econet_timer.membase[i] = of_iomap(np, i);
+ if (!econet_timer.membase[i]) {
+ pr_err("%pOFn: failed to map register [%d]\n", np, i);
+- return -ENXIO;
++ ret = -ENXIO;
++ goto err_unmap;
+ }
+ }
+
+@@ -196,21 +275,34 @@ static int __init timer_init(struct devi
+ clocksource_mmio_readl_up);
+ if (ret) {
+ pr_err("%pOFn: clocksource_mmio_init failed: %d", np, ret);
+- return ret;
++ goto err_unmap;
+ }
+
+ ret = cevt_init(np);
+ if (ret < 0)
+- return ret;
++ goto err_unmap;
++
++ cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
++ "clockevents/econet/timer:starting",
++ cevt_init_cpu, NULL);
+
+ sched_clock_register(sched_clock_read, ECONET_BITS,
+ econet_timer.freq_hz);
+
+- pr_info("%pOFn: using %u.%03u MHz high precision timer\n", np,
++ pr_info("%pOFn: using %u.%03u MHz high precision timer (%s mode)\n", np,
+ econet_timer.freq_hz / 1000000,
+- (econet_timer.freq_hz / 1000) % 1000);
++ (econet_timer.freq_hz / 1000) % 1000,
++ econet_timer.use_percpu_irq ? "percpu" : "separate IRQ");
+
+ return 0;
++
++err_unmap:
++ for (i = 0; i < num_blocks; i++) {
++ if (econet_timer.membase[i])
++ iounmap(econet_timer.membase[i]);
++ }
++ return ret;
+ }
+
+-TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
++TIMER_OF_DECLARE(econet_en751221_timer, "econet,en751221-timer", timer_init);
++TIMER_OF_DECLARE(econet_en7528_timer, "econet,en7528-timer", timer_init);
--- /dev/null
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -370,6 +370,12 @@ config SPI_DLN2
+ This driver can also be built as a module. If so, the module
+ will be called spi-dln2.
+
++config SPI_AIROHA_EN7523
++ bool "Airoha EN7523 SPI controller support"
++ depends on ARCH_AIROHA
++ help
++ This enables SPI controller support for the Airoha EN7523 SoC.
++
+ config SPI_EP93XX
+ tristate "Cirrus Logic EP93xx SPI controller"
+ depends on ARCH_EP93XX || COMPILE_TEST
+--- a/drivers/spi/Makefile
++++ b/drivers/spi/Makefile
+@@ -52,6 +52,7 @@ obj-$(CONFIG_SPI_DW_BT1) += spi-dw-bt1.
+ obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o
+ obj-$(CONFIG_SPI_DW_PCI) += spi-dw-pci.o
+ obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o
++obj-$(CONFIG_SPI_AIROHA_EN7523) += spi-en7523.o
+ obj-$(CONFIG_SPI_FALCON) += spi-falcon.o
+ obj-$(CONFIG_SPI_FSI) += spi-fsi.o
+ obj-$(CONFIG_SPI_FSL_CPM) += spi-fsl-cpm.o
+--- /dev/null
++++ b/drivers/spi/spi-en7523.c
+@@ -0,0 +1,313 @@
++// SPDX-License-Identifier: GPL-2.0
++
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/mod_devicetable.h>
++#include <linux/spi/spi.h>
++
++
++#define ENSPI_READ_IDLE_EN 0x0004
++#define ENSPI_MTX_MODE_TOG 0x0014
++#define ENSPI_RDCTL_FSM 0x0018
++#define ENSPI_MANUAL_EN 0x0020
++#define ENSPI_MANUAL_OPFIFO_EMPTY 0x0024
++#define ENSPI_MANUAL_OPFIFO_WDATA 0x0028
++#define ENSPI_MANUAL_OPFIFO_FULL 0x002C
++#define ENSPI_MANUAL_OPFIFO_WR 0x0030
++#define ENSPI_MANUAL_DFIFO_FULL 0x0034
++#define ENSPI_MANUAL_DFIFO_WDATA 0x0038
++#define ENSPI_MANUAL_DFIFO_EMPTY 0x003C
++#define ENSPI_MANUAL_DFIFO_RD 0x0040
++#define ENSPI_MANUAL_DFIFO_RDATA 0x0044
++#define ENSPI_IER 0x0090
++#define ENSPI_NFI2SPI_EN 0x0130
++
++// TODO not in spi block
++#define ENSPI_CLOCK_DIVIDER ((void __iomem *)0x1fa201c4)
++
++#define OP_CSH 0x00
++#define OP_CSL 0x01
++#define OP_CK 0x02
++#define OP_OUTS 0x08
++#define OP_OUTD 0x09
++#define OP_OUTQ 0x0A
++#define OP_INS 0x0C
++#define OP_INS0 0x0D
++#define OP_IND 0x0E
++#define OP_INQ 0x0F
++#define OP_OS2IS 0x10
++#define OP_OS2ID 0x11
++#define OP_OS2IQ 0x12
++#define OP_OD2IS 0x13
++#define OP_OD2ID 0x14
++#define OP_OD2IQ 0x15
++#define OP_OQ2IS 0x16
++#define OP_OQ2ID 0x17
++#define OP_OQ2IQ 0x18
++#define OP_OSNIS 0x19
++#define OP_ODNID 0x1A
++
++#define MATRIX_MODE_AUTO 1
++#define CONF_MTX_MODE_AUTO 0
++#define MANUALEN_AUTO 0
++#define MATRIX_MODE_MANUAL 0
++#define CONF_MTX_MODE_MANUAL 9
++#define MANUALEN_MANUAL 1
++
++#define _ENSPI_MAX_XFER 0x1ff
++
++#define REG(x) (iobase + x)
++
++
++static void __iomem *iobase;
++
++
++static void opfifo_write(u32 cmd, u32 len)
++{
++ u32 tmp = ((cmd & 0x1f) << 9) | (len & 0x1ff);
++
++ writel(tmp, REG(ENSPI_MANUAL_OPFIFO_WDATA));
++
++ /* Wait for room in OPFIFO */
++ while (readl(REG(ENSPI_MANUAL_OPFIFO_FULL)))
++ ;
++
++ /* Shift command into OPFIFO */
++ writel(1, REG(ENSPI_MANUAL_OPFIFO_WR));
++
++ /* Wait for command to finish */
++ while (!readl(REG(ENSPI_MANUAL_OPFIFO_EMPTY)))
++ ;
++}
++
++static void set_cs(int state)
++{
++ if (state)
++ opfifo_write(OP_CSH, 1);
++ else
++ opfifo_write(OP_CSL, 1);
++}
++
++static void manual_begin_cmd(void)
++{
++ /* Disable read idle state */
++ writel(0, REG(ENSPI_READ_IDLE_EN));
++
++ /* Wait for FSM to reach idle state */
++ while (readl(REG(ENSPI_RDCTL_FSM)))
++ ;
++
++ /* Set SPI core to manual mode */
++ writel(CONF_MTX_MODE_MANUAL, REG(ENSPI_MTX_MODE_TOG));
++ writel(MANUALEN_MANUAL, REG(ENSPI_MANUAL_EN));
++}
++
++static void manual_end_cmd(void)
++{
++ /* Set SPI core to auto mode */
++ writel(CONF_MTX_MODE_AUTO, REG(ENSPI_MTX_MODE_TOG));
++ writel(MANUALEN_AUTO, REG(ENSPI_MANUAL_EN));
++
++ /* Enable read idle state */
++ writel(1, REG(ENSPI_READ_IDLE_EN));
++}
++
++static void dfifo_read(u8 *buf, int len)
++{
++ int i;
++
++ for (i = 0; i < len; i++) {
++ /* Wait for requested data to show up in DFIFO */
++ while (readl(REG(ENSPI_MANUAL_DFIFO_EMPTY)))
++ ;
++ buf[i] = readl(REG(ENSPI_MANUAL_DFIFO_RDATA));
++ /* Queue up next byte */
++ writel(1, REG(ENSPI_MANUAL_DFIFO_RD));
++ }
++}
++
++static void dfifo_write(const u8 *buf, int len)
++{
++ int i;
++
++ for (i = 0; i < len; i++) {
++ /* Wait for room in DFIFO */
++ while (readl(REG(ENSPI_MANUAL_DFIFO_FULL)))
++ ;
++ writel(buf[i], REG(ENSPI_MANUAL_DFIFO_WDATA));
++ }
++}
++
++#if 0
++static void set_spi_clock_speed(int freq_mhz)
++{
++ u32 tmp, val;
++
++ tmp = readl(ENSPI_CLOCK_DIVIDER);
++ tmp &= 0xffff0000;
++ writel(tmp, ENSPI_CLOCK_DIVIDER);
++
++ val = (400 / (freq_mhz * 2));
++ tmp |= (val << 8) | 1;
++ writel(tmp, ENSPI_CLOCK_DIVIDER);
++}
++#endif
++
++static void init_hw(void)
++{
++ /* Disable manual/auto mode clash interrupt */
++ writel(0, REG(ENSPI_IER));
++
++ // TODO via clk framework
++ // set_spi_clock_speed(50);
++
++ /* Disable DMA */
++ writel(0, REG(ENSPI_NFI2SPI_EN));
++}
++
++static int xfer_read(struct spi_transfer *xfer)
++{
++ int opcode;
++ uint8_t *buf = xfer->rx_buf;
++
++ switch (xfer->rx_nbits) {
++ case SPI_NBITS_SINGLE:
++ opcode = OP_INS;
++ break;
++ case SPI_NBITS_DUAL:
++ opcode = OP_IND;
++ break;
++ case SPI_NBITS_QUAD:
++ opcode = OP_INQ;
++ break;
++ }
++
++ opfifo_write(opcode, xfer->len);
++ dfifo_read(buf, xfer->len);
++
++ return xfer->len;
++}
++
++static int xfer_write(struct spi_transfer *xfer, int next_xfer_is_rx)
++{
++ int opcode;
++ const uint8_t *buf = xfer->tx_buf;
++
++ if (next_xfer_is_rx) {
++ /* need to use Ox2Ix opcode to set the core to input afterwards */
++ switch (xfer->tx_nbits) {
++ case SPI_NBITS_SINGLE:
++ opcode = OP_OS2IS;
++ break;
++ case SPI_NBITS_DUAL:
++ opcode = OP_OS2ID;
++ break;
++ case SPI_NBITS_QUAD:
++ opcode = OP_OS2IQ;
++ break;
++ }
++ } else {
++ switch (xfer->tx_nbits) {
++ case SPI_NBITS_SINGLE:
++ opcode = OP_OUTS;
++ break;
++ case SPI_NBITS_DUAL:
++ opcode = OP_OUTD;
++ break;
++ case SPI_NBITS_QUAD:
++ opcode = OP_OUTQ;
++ break;
++ }
++ }
++
++ opfifo_write(opcode, xfer->len);
++ dfifo_write(buf, xfer->len);
++
++ return xfer->len;
++}
++
++size_t max_transfer_size(struct spi_device *spi)
++{
++ return _ENSPI_MAX_XFER;
++}
++
++int transfer_one_message(struct spi_controller *ctrl, struct spi_message *msg)
++{
++ struct spi_transfer *xfer;
++ int next_xfer_is_rx = 0;
++
++ manual_begin_cmd();
++ set_cs(0);
++ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
++ if (xfer->tx_buf) {
++ if (!list_is_last(&xfer->transfer_list, &msg->transfers)
++ && list_next_entry(xfer, transfer_list)->rx_buf != NULL)
++ next_xfer_is_rx = 1;
++ else
++ next_xfer_is_rx = 0;
++ msg->actual_length += xfer_write(xfer, next_xfer_is_rx);
++ } else if (xfer->rx_buf) {
++ msg->actual_length += xfer_read(xfer);
++ }
++ }
++ set_cs(1);
++ manual_end_cmd();
++
++ msg->status = 0;
++ spi_finalize_current_message(ctrl);
++
++ return 0;
++}
++
++static int spi_probe(struct platform_device *pdev)
++{
++ struct spi_controller *ctrl;
++ int err;
++
++ ctrl = devm_spi_alloc_master(&pdev->dev, 0);
++ if (!ctrl) {
++ dev_err(&pdev->dev, "Error allocating SPI controller\n");
++ return -ENOMEM;
++ }
++
++ iobase = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
++ if (IS_ERR(iobase)) {
++ dev_err(&pdev->dev, "Could not map SPI register address");
++ return -ENOMEM;
++ }
++
++ init_hw();
++
++ ctrl->dev.of_node = pdev->dev.of_node;
++ ctrl->flags = SPI_CONTROLLER_HALF_DUPLEX;
++ ctrl->mode_bits = SPI_RX_DUAL | SPI_TX_DUAL;
++ ctrl->max_transfer_size = max_transfer_size;
++ ctrl->transfer_one_message = transfer_one_message;
++ err = devm_spi_register_controller(&pdev->dev, ctrl);
++ if (err) {
++ dev_err(&pdev->dev, "Could not register SPI controller\n");
++ return -ENODEV;
++ }
++
++ return 0;
++}
++
++static const struct of_device_id spi_of_ids[] = {
++ { .compatible = "airoha,en7523-spi" },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, spi_of_ids);
++
++static struct platform_driver spi_driver = {
++ .probe = spi_probe,
++ .driver = {
++ .name = "airoha-en7523-spi",
++ .of_match_table = spi_of_ids,
++ },
++};
++
++module_platform_driver(spi_driver);
++
++MODULE_LICENSE("GPL v2");
++MODULE_AUTHOR("Bert Vermeulen <bert@biot.com>");
++MODULE_DESCRIPTION("Airoha EN7523 SPI driver");
--- /dev/null
+Subject: Adapt Airoha EN7523 SPI to work with EcoNet EN751221
+
+The SPI driver from Airoha EN7523 is copied here in it's original form
+so this patch makes three updates to it in order to make it work
+correctly in the EcoNet EN751221 context.
+
+The main change here is that the chip select operation is sent twice.
+This pattern is borrowed from the vendor code and it prevents write
+operations from being lost sporadically on the EN751221.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+---
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -372,7 +372,7 @@ config SPI_DLN2
+
+ config SPI_AIROHA_EN7523
+ bool "Airoha EN7523 SPI controller support"
+- depends on ARCH_AIROHA
++ depends on ARCH_AIROHA || ECONET
+ help
+ This enables SPI controller support for the Airoha EN7523 SoC.
+
+--- a/drivers/spi/spi-en7523.c
++++ b/drivers/spi/spi-en7523.c
+@@ -82,10 +82,11 @@ static void opfifo_write(u32 cmd, u32 le
+
+ static void set_cs(int state)
+ {
+- if (state)
+- opfifo_write(OP_CSH, 1);
+- else
+- opfifo_write(OP_CSL, 1);
++ u32 cmd = state ? OP_CSH : OP_CSL;
++
++ /* EN751221 drops writes if we don't send this twice. */
++ opfifo_write(cmd, 1);
++ opfifo_write(cmd, 1);
+ }
+
+ static void manual_begin_cmd(void)
+@@ -226,12 +227,12 @@ static int xfer_write(struct spi_transfe
+ return xfer->len;
+ }
+
+-size_t max_transfer_size(struct spi_device *spi)
++static size_t max_transfer_size(struct spi_device *spi)
+ {
+ return _ENSPI_MAX_XFER;
+ }
+
+-int transfer_one_message(struct spi_controller *ctrl, struct spi_message *msg)
++static int transfer_one_message(struct spi_controller *ctrl, struct spi_message *msg)
+ {
+ struct spi_transfer *xfer;
+ int next_xfer_is_rx = 0;
--- /dev/null
+spi: airoha-snfi: enable for EcoNet EN7528
+
+Enable the Airoha SNFI (SPI NAND Flash Interface) driver for EcoNet
+EN7528 SoC. The EN7528 shares the same SPI controller and NFI DMA
+engine as the Airoha EN7523/EN7581, with identical register layouts.
+
+Using the DMA-capable SNFI driver provides significantly better
+performance compared to the manual mode spi-en7523 driver.
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/spi/Kconfig
++++ b/drivers/spi/Kconfig
+@@ -59,7 +59,7 @@ comment "SPI Master Controller Drivers"
+
+ config SPI_AIROHA_SNFI
+ tristate "Airoha SPI NAND Flash Interface"
+- depends on ARCH_AIROHA || COMPILE_TEST
++ depends on ARCH_AIROHA || ECONET || COMPILE_TEST
+ depends on SPI_MASTER
+ select REGMAP_MMIO
+ help
--- /dev/null
+--- a/arch/mips/Kconfig
++++ b/arch/mips/Kconfig
+@@ -391,6 +391,7 @@ config MACH_DECSTATION
+ config ECONET
+ bool "EcoNet MIPS family"
+ select BOOT_RAW
++ select DMA_NONCOHERENT
+ select DEBUG_ZBOOT if DEBUG_KERNEL
+ select EARLY_PRINTK_8250
+ select ECONET_EN751221_TIMER
+--- a/drivers/usb/host/Kconfig
++++ b/drivers/usb/host/Kconfig
+@@ -71,7 +71,7 @@ config USB_XHCI_HISTB
+ config USB_XHCI_MTK
+ tristate "xHCI support for MediaTek SoCs"
+ select MFD_SYSCON
+- depends on (MIPS && SOC_MT7621) || ARCH_MEDIATEK || COMPILE_TEST
++ depends on (MIPS && SOC_MT7621) || ECONET || ARCH_MEDIATEK || COMPILE_TEST
+ help
+ Say 'Y' to enable the support for the xHCI host controller
+ found in MediaTek SoCs.
--- /dev/null
+gpio: en7523: enable for EcoNet MIPS platform
+
+The Airoha EN7523 GPIO controller is also found on EcoNet EN7528
+MIPS SoC. Allow building the driver when ECONET is selected.
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -271,7 +271,7 @@ config GPIO_EM
+
+ config GPIO_EN7523
+ tristate "Airoha GPIO support"
+- depends on ARCH_AIROHA
++ depends on ARCH_AIROHA || ECONET
+ default ARCH_AIROHA
+ select GPIO_GENERIC
+ select GPIOLIB_IRQCHIP
--- /dev/null
+--- /dev/null
++++ b/drivers/tty/serial/8250/8250_en7523.c
+@@ -0,0 +1,94 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Airoha EN7523 driver.
++ *
++ * Copyright (c) 2022 Genexis Sweden AB
++ * Author: Benjamin Larsson <benjamin.larsson@genexis.eu>
++ */
++#include <linux/clk.h>
++#include <linux/io.h>
++#include <linux/module.h>
++#include <linux/of_irq.h>
++#include <linux/of_platform.h>
++#include <linux/pinctrl/consumer.h>
++#include <linux/platform_device.h>
++#include <linux/pm_runtime.h>
++#include <linux/serial_8250.h>
++#include <linux/serial_reg.h>
++#include <linux/console.h>
++#include <linux/dma-mapping.h>
++#include <linux/tty.h>
++#include <linux/tty_flip.h>
++
++#include "8250.h"
++
++
++/* The Airoha UART is 16550-compatible except for the baud rate calculation.
++ *
++ * crystal_clock = 20 MHz
++ * xindiv_clock = crystal_clock / clock_div
++ * (x/y) = XYD, 32 bit register with 16 bits of x and and then 16 bits of y
++ * clock_div = XINCLK_DIVCNT (default set to 10 (0x4)),
++ * - 3 bit register [ 1, 2, 4, 8, 10, 12, 16, 20 ]
++ *
++ * baud_rate = ((xindiv_clock) * (x/y)) / ([BRDH,BRDL] * 16)
++ *
++ * XYD_y seems to need to be larger then XYD_x for things to work.
++ * Setting [BRDH,BRDL] to [0,1] and XYD_y to 65000 give even values
++ * for usual baud rates.
++ *
++ * Selecting divider needs to fulfill
++ * 1.8432 MHz <= xindiv_clk <= APB clock / 2
++ * The clocks are unknown but a divider of value 1 did not work.
++ *
++ * Optimally the XYD, BRD and XINCLK_DIVCNT registers could be searched to
++ * find values that gives the least error for every baud rate. But searching
++ * the space takes time and in practise only a few rates are of interest.
++ * With some value combinations not working a tested subset is used giving
++ * a usable range from 110 to 460800 baud.
++ */
++
++#define CLOCK_DIV_TAB_ELEMS 3
++#define XYD_Y 65000
++#define XINDIV_CLOCK 20000000
++#define UART_BRDL_20M 0x01
++#define UART_BRDH_20M 0x00
++
++static int clock_div_tab[] = { 10, 4, 2};
++static int clock_div_reg[] = { 4, 2, 1};
++
++
++int en7523_set_uart_baud_rate (struct uart_port *port, unsigned int baud)
++{
++ struct uart_8250_port *up = up_to_u8250p(port);
++ unsigned int xyd_x, nom, denom;
++ int i;
++
++ /* set DLAB to access the baud rate divider registers (BRDH, BRDL) */
++ serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB);
++
++ /* set baud rate calculation defaults */
++
++ /* set BRDIV ([BRDH,BRDL]) to 1 */
++ serial_port_out(port, UART_BRDL, UART_BRDL_20M);
++ serial_port_out(port, UART_BRDH, UART_BRDH_20M);
++
++ /* calculate XYD_x and XINCLKDR register */
++
++ for (i = 0 ; i < CLOCK_DIV_TAB_ELEMS ; i++) {
++ denom = (XINDIV_CLOCK/40) / clock_div_tab[i];
++ nom = (baud * (XYD_Y/40));
++ xyd_x = ((nom/denom) << 4);
++ if (xyd_x < XYD_Y) break;
++ }
++
++ serial_port_out(port, UART_XINCLKDR, clock_div_reg[i]);
++ serial_port_out(port, UART_XYD, (xyd_x<<16) | XYD_Y);
++
++ /* unset DLAB */
++ serial_port_out(port, UART_LCR, up->lcr);
++
++ return 0;
++}
++
++EXPORT_SYMBOL_GPL(en7523_set_uart_baud_rate);
+--- a/drivers/tty/serial/8250/8250_of.c
++++ b/drivers/tty/serial/8250/8250_of.c
+@@ -341,6 +341,7 @@ static const struct of_device_id of_plat
+ { .compatible = "ti,da830-uart", .data = (void *)PORT_DA830, },
+ { .compatible = "nuvoton,wpcm450-uart", .data = (void *)PORT_NPCM, },
+ { .compatible = "nuvoton,npcm750-uart", .data = (void *)PORT_NPCM, },
++ { .compatible = "airoha,en7523-uart", .data = (void *)PORT_AIROHA, },
+ { /* end of list */ },
+ };
+ MODULE_DEVICE_TABLE(of, of_platform_serial_table);
+--- a/drivers/tty/serial/8250/8250_port.c
++++ b/drivers/tty/serial/8250/8250_port.c
+@@ -319,6 +319,14 @@ static const struct serial8250_config ua
+ .rxtrig_bytes = {1, 8, 16, 30},
+ .flags = UART_CAP_FIFO | UART_CAP_AFE,
+ },
++ [PORT_AIROHA] = {
++ .name = "Airoha 16550",
++ .fifo_size = 8,
++ .tx_loadsz = 1,
++ .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01,
++ .rxtrig_bytes = {1, 4},
++ .flags = UART_CAP_FIFO,
++ },
+ };
+
+ /* Uart divisor latch read */
+@@ -2841,6 +2849,12 @@ serial8250_do_set_termios(struct uart_po
+
+ serial8250_set_divisor(port, baud, quot, frac);
+
++#ifdef CONFIG_SERIAL_8250_AIROHA
++ /* Airoha SoCs have custom registers for baud rate settings */
++ if (port->type == PORT_AIROHA)
++ en7523_set_uart_baud_rate(port, baud);
++#endif
++
+ /*
+ * LCR DLAB must be set to enable 64-byte FIFO mode. If the FCR
+ * is written without DLAB set, this mode will be disabled.
+--- a/drivers/tty/serial/8250/Kconfig
++++ b/drivers/tty/serial/8250/Kconfig
+@@ -355,6 +355,16 @@ config SERIAL_8250_ACORN
+ system, say Y to this option. The driver can handle 1, 2, or 3 port
+ cards. If unsure, say N.
+
++config SERIAL_8250_AIROHA
++ tristate "Airoha UART support"
++ depends on (ARCH_AIROHA || COMPILE_TEST) && OF && SERIAL_8250
++ help
++ Selecting this option enables an Airoha SoC specific baud rate
++ calculation routine on an otherwise 16550 compatible UART hardware.
++
++ If you have an Airoha based board and want to use the serial port,
++ say Y to this option. If unsure, say N.
++
+ config SERIAL_8250_BCM2835AUX
+ tristate "BCM2835 auxiliar mini UART support"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+--- a/drivers/tty/serial/8250/Makefile
++++ b/drivers/tty/serial/8250/Makefile
+@@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE) += 825
+
+ obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o
+ obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o
++obj-$(CONFIG_SERIAL_8250_AIROHA) += 8250_en7523.o
+ obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o
+ obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o
+ obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o
+--- a/include/uapi/linux/serial_reg.h
++++ b/include/uapi/linux/serial_reg.h
+@@ -383,5 +383,17 @@
+ #define UART_ALTR_EN_TXFIFO_LW 0x01 /* Enable the TX FIFO Low Watermark */
+ #define UART_ALTR_TX_LOW 0x41 /* Tx FIFO Low Watermark */
+
++/*
++ * These are definitions for the Airoha EN75XX uart registers
++ * Normalized because of 32 bits registers.
++ */
++#define UART_BRDL 0
++#define UART_BRDH 1
++#define UART_XINCLKDR 10
++#define UART_XYD 11
++#define UART_TXLVLCNT 12
++#define UART_RXLVLCNT 13
++#define UART_FINTLVL 14
++
+ #endif /* _LINUX_SERIAL_REG_H */
+
+--- a/include/uapi/linux/serial_core.h
++++ b/include/uapi/linux/serial_core.h
+@@ -31,6 +31,7 @@
+ #define PORT_ALTR_16550_F128 28 /* Altera 16550 UART with 128 FIFOs */
+ #define PORT_RT2880 29 /* Ralink RT2880 internal UART */
+ #define PORT_16550A_FSL64 30 /* Freescale 16550 UART with 64 FIFOs */
++#define PORT_AIROHA 31 /* Airoha 16550 UART */
+
+ /*
+ * ARM specific type numbers. These are not currently guaranteed
+--- a/include/linux/serial_8250.h
++++ b/include/linux/serial_8250.h
+@@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart
+ void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud,
+ unsigned int quot);
+ int fsl8250_handle_irq(struct uart_port *port);
++int en7523_set_uart_baud_rate(struct uart_port *port, unsigned int baud);
+ int serial8250_handle_irq(struct uart_port *port, unsigned int iir);
+ u16 serial8250_rx_chars(struct uart_8250_port *up, u16 lsr);
+ void serial8250_read_char(struct uart_8250_port *up, u16 lsr);
--- /dev/null
+serial: 8250: airoha: add EcoNet platform support
+
+The EcoNet EN75xx SoCs use the same UART IP core as the Airoha
+AN7523 SoCs. Add ECONET to the Kconfig dependency to enable
+the Airoha UART driver for EcoNet platforms.
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/tty/serial/8250/Kconfig
++++ b/drivers/tty/serial/8250/Kconfig
+@@ -357,7 +357,7 @@ config SERIAL_8250_ACORN
+
+ config SERIAL_8250_AIROHA
+ tristate "Airoha UART support"
+- depends on (ARCH_AIROHA || COMPILE_TEST) && OF && SERIAL_8250
++ depends on (ARCH_AIROHA || ECONET || COMPILE_TEST) && OF && SERIAL_8250
+ help
+ Selecting this option enables an Airoha SoC specific baud rate
+ calculation routine on an otherwise 16550 compatible UART hardware.
--- /dev/null
+Subject: Add EcoNet bad block table
+
+The EcoNet BBT/BMT is used for resolving bad blocks in the flash.
+It is implemented in the EcoNet bootloader so you cannot safely
+access flash without using it.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+---
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -3,7 +3,7 @@
+ nandcore-objs := core.o bbt.o
+ obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
+ obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
+-obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o
++obj-$(CONFIG_MTD_NAND_MTK_BMT) += mtk_bmt.o mtk_bmt_v2.o mtk_bmt_bbt.o mtk_bmt_nmbm.o en75_bmt.o
+ obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
+ obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
+ obj-y += onenand/
+--- a/drivers/mtd/nand/mtk_bmt.h
++++ b/drivers/mtd/nand/mtk_bmt.h
+@@ -77,6 +77,7 @@ extern struct bmt_desc bmtd;
+ extern const struct mtk_bmt_ops mtk_bmt_v2_ops;
+ extern const struct mtk_bmt_ops mtk_bmt_bbt_ops;
+ extern const struct mtk_bmt_ops mtk_bmt_nmbm_ops;
++extern const struct mtk_bmt_ops en75_bmt_ops;
+
+ static inline u32 blk_pg(u16 block)
+ {
+--- a/drivers/mtd/nand/mtk_bmt.c
++++ b/drivers/mtd/nand/mtk_bmt.c
+@@ -421,6 +421,8 @@ int mtk_bmt_attach(struct mtd_info *mtd)
+ bmtd.ops = &mtk_bmt_nmbm_ops;
+ else if (of_property_read_bool(np, "mediatek,bbt"))
+ bmtd.ops = &mtk_bmt_bbt_ops;
++ else if (of_property_read_bool(np, "econet,bmt"))
++ bmtd.ops = &en75_bmt_ops;
+ else
+ return 0;
+
--- /dev/null
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -19,6 +19,7 @@
+ #include <linux/string.h>
+ #include <linux/spi/spi.h>
+ #include <linux/spi/spi-mem.h>
++#include <linux/mtd/mtk_bmt.h>
+
+ static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
+ {
+@@ -1604,6 +1605,7 @@ static int spinand_probe(struct spi_mem
+ if (ret)
+ return ret;
+
++ mtk_bmt_attach(mtd);
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret)
+ goto err_spinand_cleanup;
+@@ -1611,6 +1613,7 @@ static int spinand_probe(struct spi_mem
+ return 0;
+
+ err_spinand_cleanup:
++ mtk_bmt_detach(mtd);
+ spinand_cleanup(spinand);
+
+ return ret;
+@@ -1629,6 +1632,7 @@ static int spinand_remove(struct spi_mem
+ if (ret)
+ return ret;
+
++ mtk_bmt_detach(mtd);
+ spinand_cleanup(spinand);
+
+ return 0;
--- /dev/null
+From f3fa5911b1f094e164c497f7b10d94d92852e285 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 14 Jan 2026 17:54:05 +0000
+Subject: [PATCH] dt-bindings: clock, reset: Add econet EN751221 bindings
+
+Add clock and reset bindings for EN751221 based on the Airoha EN7523 SCU
+driver.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+---
+The EN751221 has the same bifurcation of SCU as EN7581 so we use
+the same chip-scu as airoha,en7581-chip-scu.
+---
+ .../bindings/clock/airoha,en7523-scu.yaml | 17 +++++++-
+ .../mips/econet,en751221-chip-scu.yaml | 42 +++++++++++++++++++
+ .../dt-bindings/clock/econet,en751221-scu.h | 14 +++++++
+ .../dt-bindings/reset/econet,en751221-scu.h | 41 ++++++++++++++++++
+ 4 files changed, 113 insertions(+), 1 deletion(-)
+ create mode 100644 Documentation/devicetree/bindings/mips/econet,en751221-chip-scu.yaml
+ create mode 100644 include/dt-bindings/clock/econet,en751221-scu.h
+ create mode 100644 include/dt-bindings/reset/econet,en751221-scu.h
+
+--- a/Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml
++++ b/Documentation/devicetree/bindings/clock/airoha,en7523-scu.yaml
+@@ -32,6 +32,7 @@ properties:
+ - enum:
+ - airoha,en7523-scu
+ - airoha,en7581-scu
++ - econet,en751221-scu
+
+ reg:
+ items:
+@@ -67,7 +68,10 @@ allOf:
+ - if:
+ properties:
+ compatible:
+- const: airoha,en7581-scu
++ items:
++ - enum:
++ - airoha,en7581-scu
++ - econet,en751221-scu
+ then:
+ properties:
+ reg:
+@@ -98,3 +102,14 @@ examples:
+ #reset-cells = <1>;
+ };
+ };
++
++ - |
++ soc {
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ scuclk: clock-controller@1fb00000 {
++ compatible = "econet,en751221-scu";
++ reg = <0x1fb00000 0x970>;
++ };
++ };
+\ No newline at end of file
+--- /dev/null
++++ b/Documentation/devicetree/bindings/mips/econet,en751221-chip-scu.yaml
+@@ -0,0 +1,42 @@
++# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
++%YAML 1.2
++---
++$id: http://devicetree.org/schemas/arm/airoha,en7581-chip-scu.yaml#
++$schema: http://devicetree.org/meta-schemas/core.yaml#
++
++title: EcoNet Chip SCU Controller for EN751221 SoC
++
++maintainers:
++ - Caleb James DeLisle <cjd@cjdns.fr>
++
++description:
++ The EcoNet chip-scu block provides a configuration interface for clock,
++ io-muxing and other functionalities used by multiple controllers (e.g. clock,
++ pinctrl, ecc) on EN751221 SoC.
++
++properties:
++ compatible:
++ items:
++ - enum:
++ - econet,en751221-chip-scu
++ - const: syscon
++
++ reg:
++ maxItems: 1
++
++required:
++ - compatible
++ - reg
++
++additionalProperties: false
++
++examples:
++ - |
++ soc {
++ #address-cells = <1>;
++ #size-cells = <1>;
++ syscon@1fa20000 {
++ compatible = "econet,en751221-chip-scu", "syscon";
++ reg = <0x1fa20000 0x388>;
++ };
++ };
+--- /dev/null
++++ b/include/dt-bindings/clock/econet,en751221-scu.h
+@@ -0,0 +1,15 @@
++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
++
++#ifndef _DT_BINDINGS_CLOCK_ECONET_EN751221_SCU_H_
++#define _DT_BINDINGS_CLOCK_ECONET_EN751221_SCU_H_
++
++#define EN751221_CLK_PCIE 0
++#define EN751221_CLK_SPI 1
++#define EN751221_CLK_BUS 2
++#define EN751221_CLK_CPU 3
++#define EN751221_CLK_HPT 4
++#define EN751221_CLK_GSW 5
++
++#define EN751221_MAX_CLKS 6
++
++#endif /* _DT_BINDINGS_CLOCK_ECONET_EN751221_SCU_H_ */
+\ No newline at end of file
+--- /dev/null
++++ b/include/dt-bindings/reset/econet,en751221-scu.h
+@@ -0,0 +1,49 @@
++/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
++
++#ifndef __DT_BINDINGS_RESET_CONTROLLER_ECONET_EN751221_H_
++#define __DT_BINDINGS_RESET_CONTROLLER_ECONET_EN751221_H_
++
++#define EN751221_XPON_PHY_RST 0
++#define EN751221_PCM1_ZSI_ISI_RST 1
++#define EN751221_FE_QDMA1_RST 2
++#define EN751221_FE_QDMA2_RST 3
++#define EN751221_FE_UNZIP_RST 4
++#define EN751221_PCM2_RST 5
++#define EN751221_PTM_MAC_RST 6
++#define EN751221_CRYPTO_RST 7
++#define EN751221_SAR_RST 8
++#define EN751221_TIMER_RST 9
++#define EN751221_INTC_RST 10
++#define EN751221_BONDING_RST 11
++#define EN751221_PCM1_RST 12
++#define EN751221_UART_RST 13
++#define EN751221_GPIO_RST 14
++#define EN751221_GDMA_RST 15
++#define EN751221_I2C_MASTER_RST 16
++#define EN751221_PCM2_ZSI_ISI_RST 17
++#define EN751221_SFC_RST 18
++#define EN751221_UART2_RST 19
++#define EN751221_GDMP_RST 20
++#define EN751221_FE_RST 21
++#define EN751221_USB_HOST_P0_RST 22
++#define EN751221_GSW_RST 23
++#define EN751221_SFC2_PCM_RST 24
++#define EN751221_PCIE0_RST 25
++#define EN751221_PCIE1_RST 26
++#define EN751221_CPU_TIMER_RST 27
++#define EN751221_PCIE_HB_RST 28
++#define EN751221_SIMIF_RST 29
++#define EN751221_XPON_MAC_RST 30
++#define EN751221_GFAST_RST 31
++#define EN751221_CPU_TIMER2_RST 32
++#define EN751221_UART3_RST 33
++#define EN751221_UART4_RST 34
++#define EN751221_UART5_RST 35
++#define EN751221_I2C2_RST 36
++#define EN751221_XSI_MAC_RST 37
++#define EN751221_XSI_PHY_RST 38
++#define EN751221_DMT_RST 39
++#define EN751221_USB_PHY_P0_RST 40
++#define EN751221_USB_PHY_P1_RST 41
++
++#endif /* __DT_BINDINGS_RESET_CONTROLLER_ECONET_EN751221_H_ */
--- /dev/null
+From 1dfb29374a040ba80d378b3465f4e0bcb67f4ba3 Mon Sep 17 00:00:00 2001
+From: Caleb James DeLisle <cjd@cjdns.fr>
+Date: Wed, 14 Jan 2026 18:06:13 +0000
+Subject: [PATCH] clk: airoha: Add econet EN751221 clock/reset support to
+ en7523-scu
+
+EcoNet EN751221 clock/reset driver is significantly similar to the
+EN7523 / EN7581, however the EN751221 does not have a neat batch of clock
+divider registers so there are fewer known clocks, and the frequency of
+each clock is derived differently. This clock driver will probably work
+correctly on EN751627, EN7528, and EN7580.
+
+Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
+---
+ drivers/clk/Kconfig | 6 +-
+ drivers/clk/clk-en7523.c | 182 +++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 185 insertions(+), 3 deletions(-)
+
+--- a/drivers/clk/Kconfig
++++ b/drivers/clk/Kconfig
+@@ -210,13 +210,13 @@ config COMMON_CLK_CS2000_CP
+ If you say yes here you get support for the CS2000 clock multiplier.
+
+ config COMMON_CLK_EN7523
+- bool "Clock driver for Airoha EN7523 SoC system clocks"
++ bool "Clock driver for Airoha/EcoNet SoC system clocks"
+ depends on OF
+- depends on ARCH_AIROHA || COMPILE_TEST
++ depends on ARCH_AIROHA || ECONET || COMPILE_TEST
+ default ARCH_AIROHA
+ help
+ This driver provides the fixed clocks and gates present on Airoha
+- ARM silicon.
++ and EcoNet silicon.
+
+ config COMMON_CLK_EP93XX
+ tristate "Clock driver for Cirrus Logic ep93xx SoC"
+--- a/drivers/clk/clk-en7523.c
++++ b/drivers/clk/clk-en7523.c
+@@ -1,5 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+
++#include <linux/bitfield.h>
+ #include <linux/delay.h>
+ #include <linux/clk-provider.h>
+ #include <linux/io.h>
+@@ -11,6 +12,8 @@
+ #include <dt-bindings/clock/en7523-clk.h>
+ #include <dt-bindings/reset/airoha,en7523-reset.h>
+ #include <dt-bindings/reset/airoha,en7581-reset.h>
++#include <dt-bindings/clock/econet,en751221-scu.h>
++#include <dt-bindings/reset/econet,en751221-scu.h>
+
+ #define RST_NR_PER_BANK 32
+
+@@ -33,15 +36,47 @@
+ #define REG_RESET_CONTROL_PCIEHB BIT(29)
+ #define REG_RESET_CONTROL_PCIE1 BIT(27)
+ #define REG_RESET_CONTROL_PCIE2 BIT(26)
++#define REG_HIR 0x064
++#define REG_HIR_MASK GENMASK(31, 16)
+ /* EN7581 */
+ #define REG_NP_SCU_PCIC 0x88
+ #define REG_NP_SCU_SSTR 0x9c
+ #define REG_PCIE_XSI0_SEL_MASK GENMASK(14, 13)
+ #define REG_PCIE_XSI1_SEL_MASK GENMASK(12, 11)
+ #define REG_CRYPTO_CLKSRC2 0x20c
++/* EN751221 */
++#define EN751221_REG_SPI_DIV 0x0cc
++#define EN751221_REG_SPI_DIV_MASK GENMASK(31,8)
++#define EN751221_SPI_BASE 500000000
++#define EN751221_SPI_BASE_EN7526C 400000000
++#define EN751221_REG_BUS 0x284
++#define EN751221_REG_BUS_MASK GENMASK(21,12)
++#define EN751221_REG_SSR3 0x094
++#define EN751221_REG_SSR3_GSW_MASK GENMASK(9,8)
+
+ #define REG_RST_CTRL2 0x830
+ #define REG_RST_CTRL1 0x834
++#define EN751221_REG_RST_DMT 0x84
++#define EN751221_REG_RST_USB 0xec
++
++enum en_hir {
++ HIR_UNKNOWN = -1,
++ HIR_TC3169 = 0,
++ HIR_TC3182 = 1,
++ HIR_RT65168 = 2,
++ HIR_RT63165 = 3,
++ HIR_RT63365 = 4,
++ HIR_MT751020 = 5,
++ HIR_MT7505 = 6,
++ HIR_EN751221 = 7,
++ HIR_EN7526C = 8,
++ HIR_EN751627 = 9,
++ HIR_EN7580 = 10,
++ HIR_EN7528 = 11,
++ HIR_EN7523 = 12,
++ HIR_EN7581 = 13,
++ HIR_MAX = 14,
++};
+
+ struct en_clk_desc {
+ int id;
+@@ -93,6 +128,8 @@ static const u32 bus7581_base[] = { 6000
+ static const u32 npu7581_base[] = { 800000000, 750000000, 720000000, 600000000 };
+ static const u32 crypto_base[] = { 540000000, 480000000 };
+ static const u32 emmc7581_base[] = { 200000000, 150000000 };
++/* EN751221 */
++static const u32 gsw751221_base[] = { 500000000, 250000000, 400000000, 200000000 };
+
+ static const struct en_clk_desc en7523_base_clks[] = {
+ {
+@@ -300,6 +337,13 @@ static const u16 en7581_rst_ofs[] = {
+ REG_RST_CTRL1,
+ };
+
++static const u16 en751221_rst_ofs[] = {
++ REG_RST_CTRL2,
++ REG_RST_CTRL1,
++ EN751221_REG_RST_DMT,
++ EN751221_REG_RST_USB,
++};
++
+ static const u16 en7523_rst_map[] = {
+ /* RST_CTRL2 */
+ [EN7523_XPON_PHY_RST] = 0,
+@@ -405,8 +449,61 @@ static const u16 en7581_rst_map[] = {
+ [EN7581_XPON_MAC_RST] = RST_NR_PER_BANK + 31,
+ };
+
++static const u16 en751221_rst_map[] = {
++ /* RST_CTRL2 */
++ [EN751221_XPON_PHY_RST] = 0,
++ [EN751221_GFAST_RST] = 1,
++ [EN751221_CPU_TIMER2_RST] = 2,
++ [EN751221_UART3_RST] = 3,
++ [EN751221_UART4_RST] = 4,
++ [EN751221_UART5_RST] = 5,
++ [EN751221_I2C2_RST] = 6,
++ [EN751221_XSI_MAC_RST] = 7,
++ [EN751221_XSI_PHY_RST] = 8,
++
++ /* RST_CTRL1 */
++ [EN751221_PCM1_ZSI_ISI_RST] = RST_NR_PER_BANK + 0,
++ [EN751221_FE_QDMA1_RST] = RST_NR_PER_BANK + 1,
++ [EN751221_FE_QDMA2_RST] = RST_NR_PER_BANK + 2,
++ [EN751221_FE_UNZIP_RST] = RST_NR_PER_BANK + 3,
++ [EN751221_PCM2_RST] = RST_NR_PER_BANK + 4,
++ [EN751221_PTM_MAC_RST] = RST_NR_PER_BANK + 5,
++ [EN751221_CRYPTO_RST] = RST_NR_PER_BANK + 6,
++ [EN751221_SAR_RST] = RST_NR_PER_BANK + 7,
++ [EN751221_TIMER_RST] = RST_NR_PER_BANK + 8,
++ [EN751221_INTC_RST] = RST_NR_PER_BANK + 9,
++ [EN751221_BONDING_RST] = RST_NR_PER_BANK + 10,
++ [EN751221_PCM1_RST] = RST_NR_PER_BANK + 11,
++ [EN751221_UART_RST] = RST_NR_PER_BANK + 12,
++ [EN751221_GPIO_RST] = RST_NR_PER_BANK + 13,
++ [EN751221_GDMA_RST] = RST_NR_PER_BANK + 14,
++ [EN751221_I2C_MASTER_RST] = RST_NR_PER_BANK + 16,
++ [EN751221_PCM2_ZSI_ISI_RST] = RST_NR_PER_BANK + 17,
++ [EN751221_SFC_RST] = RST_NR_PER_BANK + 18,
++ [EN751221_UART2_RST] = RST_NR_PER_BANK + 19,
++ [EN751221_GDMP_RST] = RST_NR_PER_BANK + 20,
++ [EN751221_FE_RST] = RST_NR_PER_BANK + 21,
++ [EN751221_USB_HOST_P0_RST] = RST_NR_PER_BANK + 22,
++ [EN751221_GSW_RST] = RST_NR_PER_BANK + 23,
++ [EN751221_SFC2_PCM_RST] = RST_NR_PER_BANK + 25,
++ [EN751221_PCIE0_RST] = RST_NR_PER_BANK + 26,
++ [EN751221_PCIE1_RST] = RST_NR_PER_BANK + 27,
++ [EN751221_CPU_TIMER_RST] = RST_NR_PER_BANK + 28,
++ [EN751221_PCIE_HB_RST] = RST_NR_PER_BANK + 29,
++ [EN751221_SIMIF_RST] = RST_NR_PER_BANK + 30,
++ [EN751221_XPON_MAC_RST] = RST_NR_PER_BANK + 31,
++
++ /* RST_DMT */
++ [EN751221_DMT_RST] = 2 * RST_NR_PER_BANK + 0,
++
++ /* RST_USB */
++ [EN751221_USB_PHY_P0_RST] = 3 * RST_NR_PER_BANK + 6,
++ [EN751221_USB_PHY_P1_RST] = 3 * RST_NR_PER_BANK + 7,
++};
++
+ static int en7581_reset_register(struct device *dev, void __iomem *base,
+- const u16 *rst_map, int nr_resets);
++ const u16 *rst_map, int nr_resets,
++ const u16 *rst_reg_ofs);
+
+ static u32 en7523_get_base_rate(const struct en_clk_desc *desc, u32 val)
+ {
+@@ -604,7 +701,8 @@ static int en7523_clk_hw_init(struct pla
+ en7523_register_clocks(&pdev->dev, clk_data, base, np_base);
+
+ return en7581_reset_register(&pdev->dev, np_base, en7523_rst_map,
+- ARRAY_SIZE(en7523_rst_map));
++ ARRAY_SIZE(en7523_rst_map),
++ en7581_rst_ofs);
+ }
+
+ static void en7581_register_clocks(struct device *dev, struct clk_hw_onecell_data *clk_data,
+@@ -705,7 +803,8 @@ static const struct reset_control_ops en
+ };
+
+ static int en7581_reset_register(struct device *dev, void __iomem *base,
+- const u16 *rst_map, int nr_resets)
++ const u16 *rst_map, int nr_resets,
++ const u16 *rst_reg_ofs)
+ {
+ struct en_rst_data *rst_data;
+
+@@ -713,7 +812,7 @@ static int en7581_reset_register(struct
+ if (!rst_data)
+ return -ENOMEM;
+
+- rst_data->bank_ofs = en7581_rst_ofs;
++ rst_data->bank_ofs = rst_reg_ofs;
+ rst_data->idx_map = rst_map;
+ rst_data->base = base;
+
+@@ -752,7 +851,123 @@ static int en7581_clk_hw_init(struct pla
+ writel(val | 3, base + REG_NP_SCU_PCIC);
+
+ return en7581_reset_register(&pdev->dev, base, en7581_rst_map,
+- ARRAY_SIZE(en7581_rst_map));
++ ARRAY_SIZE(en7581_rst_map),
++ en7581_rst_ofs);
++}
++
++static enum en_hir get_hw_id(void __iomem *np_base)
++{
++ u32 val = FIELD_GET(REG_HIR_MASK, readl(np_base + REG_HIR));
++
++ if (val < HIR_MAX)
++ return (enum en_hir) val;
++
++ return HIR_UNKNOWN;
++}
++
++static void en751221_try_register_clk(struct device *dev, int key,
++ struct clk_hw_onecell_data *clk_data,
++ const char *name, u32 rate)
++{
++ struct clk_hw *hw;
++
++ hw = clk_hw_register_fixed_rate(dev, name, NULL, 0, rate);
++ if (IS_ERR(hw))
++ pr_err("Failed to register clk %s: %pe\n", name, hw);
++ else
++ clk_data->hws[key] = hw;
++}
++
++static void en751221_register_clocks(struct device *dev,
++ struct clk_hw_onecell_data *clk_data,
++ struct regmap *map, void __iomem *np_base)
++{
++ enum en_hir hid = get_hw_id(np_base);
++ struct clk_hw *hw;
++ u32 rate;
++ u32 div;
++ int err;
++
++ /* PCI */
++ hw = en7523_register_pcie_clk(dev, np_base);
++ clk_data->hws[EN751221_CLK_PCIE] = hw;
++
++ /* SPI */
++ rate = EN751221_SPI_BASE;
++ if (hid == HIR_EN7526C)
++ rate = EN751221_SPI_BASE_EN7526C;
++
++ err = regmap_read(map, EN751221_REG_SPI_DIV, &div);
++ if (err) {
++ pr_err("Failed reading fixed clk div %s: %d\n",
++ "spi", err);
++ } else {
++ div = FIELD_GET(EN751221_REG_SPI_DIV_MASK, div) * 2;
++ if (!div)
++ div = 40;
++
++ en751221_try_register_clk(dev, EN751221_CLK_SPI, clk_data,
++ "spi", rate / div);
++ }
++
++ /* BUS */
++ rate = FIELD_GET(EN751221_REG_BUS_MASK,
++ readl(np_base + EN751221_REG_BUS));
++ rate *= 1000000;
++ en751221_try_register_clk(dev, EN751221_CLK_BUS, clk_data, "bus",
++ rate);
++
++ /* CPU */
++ en751221_try_register_clk(dev, EN751221_CLK_CPU, clk_data, "cpu",
++ rate * 4);
++
++ /* HPT */
++ switch (hid) {
++ case HIR_EN751221:
++ case HIR_EN751627:
++ case HIR_EN7526C:
++ case HIR_EN7580:
++ case HIR_EN7528:
++ rate = 200000000;
++ break;
++ case HIR_MT7505:
++ rate = 100000000;
++ break;
++ case HIR_MT751020:
++ rate = 800000000 / 3;
++ break;
++ default:
++ rate = 250000000;
++ }
++ en751221_try_register_clk(dev, EN751221_CLK_HPT, clk_data, "hpt",
++ rate);
++
++ /* GSW */
++ rate = FIELD_GET(EN751221_REG_SSR3_GSW_MASK,
++ readl(np_base + EN751221_REG_SSR3));
++ en751221_try_register_clk(dev, EN751221_CLK_GSW, clk_data, "gsw",
++ gsw751221_base[rate]);
++}
++
++static int en751221_clk_hw_init(struct platform_device *pdev,
++ struct clk_hw_onecell_data *clk_data)
++{
++ struct regmap *map;
++ void __iomem *base;
++
++ map = syscon_regmap_lookup_by_compatible("econet,en751221-chip-scu");
++ if (IS_ERR(map))
++ return PTR_ERR(map);
++
++ base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(base))
++ return PTR_ERR(base);
++
++ en751221_register_clocks(&pdev->dev, clk_data, map, base);
++
++ return en7581_reset_register(&pdev->dev, base, en751221_rst_map,
++ ARRAY_SIZE(en751221_rst_map),
++ en751221_rst_ofs);
+ }
+
+ static int en7523_clk_probe(struct platform_device *pdev)
+@@ -799,9 +1014,20 @@ static const struct en_clk_soc_data en75
+ .hw_init = en7581_clk_hw_init,
+ };
+
++static const struct en_clk_soc_data en751221_data = {
++ .num_clocks = EN751221_MAX_CLKS,
++ .pcie_ops = {
++ .is_enabled = en7523_pci_is_enabled,
++ .prepare = en7523_pci_prepare,
++ .unprepare = en7523_pci_unprepare,
++ },
++ .hw_init = en751221_clk_hw_init,
++};
++
+ static const struct of_device_id of_match_clk_en7523[] = {
+ { .compatible = "airoha,en7523-scu", .data = &en7523_data },
+ { .compatible = "airoha,en7581-scu", .data = &en7581_data },
++ { .compatible = "econet,en751221-scu", .data = &en751221_data },
+ { /* sentinel */ }
+ };
+
--- /dev/null
+--- a/drivers/pci/controller/Kconfig
++++ b/drivers/pci/controller/Kconfig
+@@ -187,7 +187,7 @@ config PCI_MVEBU
+
+ config PCIE_MEDIATEK
+ tristate "MediaTek PCIe controller"
+- depends on ARCH_AIROHA || ARCH_MEDIATEK || COMPILE_TEST
++ depends on ARCH_AIROHA || ARCH_MEDIATEK || ECONET || COMPILE_TEST
+ depends on OF
+ depends on PCI_MSI
+ help
+--- a/arch/mips/econet/Kconfig
++++ b/arch/mips/econet/Kconfig
+@@ -14,7 +14,9 @@ choice
+ select COMMON_CLK
+ select CPU_BIG_ENDIAN
+ select ECONET_EN751221_INTC
++ select HAVE_PCI
+ select IRQ_MIPS_CPU
++ select PCI_DRIVERS_GENERIC
+ select SMP
+ select SMP_UP
+ select SYS_SUPPORTS_SMP
+@@ -28,9 +30,11 @@ choice
+ bool "EN7528 family"
+ select COMMON_CLK
+ select CPU_LITTLE_ENDIAN
++ select HAVE_PCI
+ select IRQ_MIPS_CPU
+ select MIPS_CPU_SCACHE
+ select MIPS_GIC
++ select PCI_DRIVERS_GENERIC
+ select SMP
+ select SMP_UP
+ select SYS_SUPPORTS_HIGHMEM
+--- a/drivers/pci/controller/pcie-mediatek.c
++++ b/drivers/pci/controller/pcie-mediatek.c
+@@ -76,6 +76,7 @@
+
+ #define PCIE_CONF_VEND_ID 0x100
+ #define PCIE_CONF_DEVICE_ID 0x102
++#define PCIE_CONF_REV_CLASS 0x104
+ #define PCIE_CONF_CLASS_ID 0x106
+
+ #define PCIE_INT_MASK 0x420
+@@ -88,6 +89,11 @@
+ #define MSI_MASK BIT(23)
+ #define MTK_MSI_IRQS_NUM 32
+
++#define EN7528_HOST_MODE 0x00804201
++#define EN7528_LINKUP_REG 0x50
++#define EN7528_RC0_LINKUP BIT(1)
++#define EN7528_RC1_LINKUP BIT(2)
++
+ #define PCIE_AHB_TRANS_BASE0_L 0x438
+ #define PCIE_AHB_TRANS_BASE0_H 0x43c
+ #define AHB2PCIE_SIZE(x) ((x) & GENMASK(4, 0))
+@@ -750,6 +756,86 @@ static int mtk_pcie_startup_port_v2(stru
+ return 0;
+ }
+
++static int mtk_pcie_startup_port_en7528(struct mtk_pcie_port *port)
++{
++ struct mtk_pcie *pcie = port->pcie;
++ struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
++ struct resource *mem = NULL;
++ struct resource_entry *entry;
++ u32 val, link_mask;
++ int err;
++
++ entry = resource_list_first_type(&host->windows, IORESOURCE_MEM);
++ if (entry)
++ mem = entry->res;
++ if (!mem)
++ return -EINVAL;
++
++ if (!pcie->cfg) {
++ dev_err(pcie->dev, "EN7528: pciecfg syscon not available\n");
++ return -EINVAL;
++ }
++
++ /* Assert all reset signals */
++ writel(0, port->base + PCIE_RST_CTRL);
++
++ /*
++ * Enable PCIe link down reset, if link status changed from link up to
++ * link down, this will reset MAC control registers and configuration
++ * space.
++ */
++ writel(PCIE_LINKDOWN_RST_EN, port->base + PCIE_RST_CTRL);
++
++ /*
++ * Described in PCIe CEM specification sections 2.2 (PERST# Signal) and
++ * 2.2.1 (Initial Power-Up (G3 to S0)). The deassertion of PERST#
++ * should be delayed 100ms (TPVPERL) for the power and clock to become
++ * stable.
++ */
++ msleep(100);
++
++ /* De-assert PHY, PE, PIPE, MAC and configuration reset */
++ val = readl(port->base + PCIE_RST_CTRL);
++ val |= PCIE_PHY_RSTB | PCIE_PERSTB | PCIE_PIPE_SRSTB |
++ PCIE_MAC_SRSTB | PCIE_CRSTB;
++ writel(val, port->base + PCIE_RST_CTRL);
++
++ writel(PCIE_CLASS_CODE | PCIE_REVISION_ID,
++ port->base + PCIE_CONF_REV_CLASS);
++ writel(EN7528_HOST_MODE, port->base);
++
++ link_mask = (port->slot == 0) ? EN7528_RC0_LINKUP : EN7528_RC1_LINKUP;
++
++ /* 100ms timeout value should be enough for Gen1/2 training */
++ err = regmap_read_poll_timeout(pcie->cfg, EN7528_LINKUP_REG, val,
++ !!(val & link_mask), 20,
++ 100 * USEC_PER_MSEC);
++ if (err) {
++ dev_err(pcie->dev, "EN7528: port%d link timeout\n", port->slot);
++ return -ETIMEDOUT;
++ }
++
++ /* Set INTx mask */
++ val = readl(port->base + PCIE_INT_MASK);
++ val &= ~INTX_MASK;
++ writel(val, port->base + PCIE_INT_MASK);
++
++ if (IS_ENABLED(CONFIG_PCI_MSI))
++ mtk_pcie_enable_msi(port);
++
++ /* Set AHB to PCIe translation windows */
++ val = lower_32_bits(mem->start) |
++ AHB2PCIE_SIZE(fls(resource_size(mem)));
++ writel(val, port->base + PCIE_AHB_TRANS_BASE0_L);
++
++ val = upper_32_bits(mem->start);
++ writel(val, port->base + PCIE_AHB_TRANS_BASE0_H);
++
++ writel(WIN_ENABLE, port->base + PCIE_AXI_WINDOW0);
++
++ return 0;
++}
++
+ static void __iomem *mtk_pcie_map_bus(struct pci_bus *bus,
+ unsigned int devfn, int where)
+ {
+@@ -1116,6 +1202,20 @@ static int mtk_pcie_probe(struct platfor
+ if (err)
+ goto put_resources;
+
++ /* Retrain Gen1 links to reach Gen2 where supported */
++ if (pcie->soc->startup == mtk_pcie_startup_port_en7528) {
++ struct pci_bus *bus = host->bus;
++ struct pci_dev *rc = NULL;
++
++ while ((rc = pci_get_class(PCI_CLASS_BRIDGE_PCI << 8, rc))) {
++ if (rc->bus != bus)
++ continue;
++ if (!pcie_retrain_link(rc, true))
++ dev_info(dev, "port%d link retrained\n",
++ PCI_SLOT(rc->devfn));
++ }
++ }
++
+ return 0;
+
+ put_resources:
+@@ -1225,12 +1325,19 @@ static const struct mtk_pcie_soc mtk_pci
+ .setup_irq = mtk_pcie_setup_irq,
+ };
+
++static const struct mtk_pcie_soc mtk_pcie_soc_en7528 = {
++ .ops = &mtk_pcie_ops_v2,
++ .startup = mtk_pcie_startup_port_en7528,
++ .setup_irq = mtk_pcie_setup_irq,
++};
++
+ static const struct of_device_id mtk_pcie_ids[] = {
+ { .compatible = "mediatek,mt2701-pcie", .data = &mtk_pcie_soc_v1 },
+ { .compatible = "mediatek,mt7623-pcie", .data = &mtk_pcie_soc_v1 },
+ { .compatible = "mediatek,mt2712-pcie", .data = &mtk_pcie_soc_mt2712 },
+ { .compatible = "mediatek,mt7622-pcie", .data = &mtk_pcie_soc_mt7622 },
+ { .compatible = "mediatek,mt7629-pcie", .data = &mtk_pcie_soc_mt7629 },
++ { .compatible = "econet,en7528-pcie", .data = &mtk_pcie_soc_en7528 },
+ {},
+ };
+ MODULE_DEVICE_TABLE(of, mtk_pcie_ids);
+--- a/drivers/phy/Kconfig
++++ b/drivers/phy/Kconfig
+@@ -82,6 +82,17 @@ config PHY_AIROHA_PCIE
+ This driver create the basic PHY instance and provides initialize
+ callback for PCIe GEN3 port.
+
++config PHY_EN7528_PCIE
++ tristate "EcoNet EN7528 PCIe PHY Driver"
++ depends on ECONET || COMPILE_TEST
++ depends on OF
++ select GENERIC_PHY
++ select REGMAP_MMIO
++ help
++ Say Y here to add support for EcoNet EN7528 PCIe PHY driver.
++ This driver provides PHY initialization for the two PCIe ports
++ on EN7528 SoC.
++
+ source "drivers/phy/allwinner/Kconfig"
+ source "drivers/phy/amlogic/Kconfig"
+ source "drivers/phy/broadcom/Kconfig"
+--- a/drivers/phy/Makefile
++++ b/drivers/phy/Makefile
+@@ -11,6 +11,7 @@ obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
+ obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
+ obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
+ obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
++obj-$(CONFIG_PHY_EN7528_PCIE) += phy-en7528-pcie.o
+ obj-y += allwinner/ \
+ amlogic/ \
+ broadcom/ \
+--- /dev/null
++++ b/drivers/phy/phy-en7528-pcie.c
+@@ -0,0 +1,155 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Copyright (C) 2026 Ahmed Naseef <naseefkm@gmail.com>
++ *
++ * EcoNet EN7528 PCIe PHY Driver
++ */
++
++#include <linux/bitops.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/phy/phy.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++struct en7528_pcie_phy_op {
++ u32 reg;
++ u32 mask;
++ u32 val;
++};
++
++struct en7528_pcie_phy {
++ struct regmap *regmap;
++ const struct en7528_pcie_phy_op *data;
++};
++
++/* Port 0 PHY: set LCDDS_CLK_PH_INV for PLL operation */
++static const struct en7528_pcie_phy_op en7528_phy_port0[] = {
++ {
++ .reg = 0x4a0,
++ .mask = BIT(5),
++ .val = BIT(5),
++ },
++ { /* sentinel */ }
++};
++
++/* Port 1 PHY: Rx impedance tuning, target R -5 Ohm */
++static const struct en7528_pcie_phy_op en7528_phy_port1[] = {
++ {
++ .reg = 0xb2c,
++ .mask = GENMASK(13, 12),
++ .val = BIT(12),
++ },
++ { /* sentinel */ }
++};
++
++/* EN751221 Port 1 PHY */
++static const struct en7528_pcie_phy_op en751221_phy_port1[] = {
++ /* Rx Detection Timing for 7512 E1, 16*8 clock cycles */
++ {
++ .reg = 0xa28,
++ .mask = GENMASK(17, 9),
++ .val = 16 << 9,
++ },
++ /* Same for different power mode */
++ {
++ .reg = 0xa2c,
++ .mask = GENMASK(8, 0),
++ .val = 16,
++ },
++ { /* sentinel */ }
++};
++
++static int en7528_pcie_phy_init(struct phy *phy)
++{
++ struct en7528_pcie_phy *ephy = phy_get_drvdata(phy);
++ const struct en7528_pcie_phy_op *data = ephy->data;
++ int i, ret;
++
++ for (i = 0; data[i].mask || data[i].val; i++) {
++ if (i)
++ usleep_range(1000, 2000);
++
++ ret = regmap_update_bits(ephy->regmap, data[i].reg,
++ data[i].mask, data[i].val);
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
++
++static const struct phy_ops en7528_pcie_phy_ops = {
++ .init = en7528_pcie_phy_init,
++ .owner = THIS_MODULE,
++};
++
++static int en7528_pcie_phy_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ const struct en7528_pcie_phy_op *data;
++ struct regmap_config regmap_config = {
++ .reg_bits = 32,
++ .val_bits = 32,
++ .reg_stride = 4,
++ };
++ struct phy_provider *provider;
++ struct en7528_pcie_phy *ephy;
++ void __iomem *base;
++ struct phy *phy;
++ int i;
++
++ data = of_device_get_match_data(dev);
++ if (!data)
++ return -EINVAL;
++
++ ephy = devm_kzalloc(dev, sizeof(*ephy), GFP_KERNEL);
++ if (!ephy)
++ return -ENOMEM;
++
++ ephy->data = data;
++
++ base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(base))
++ return PTR_ERR(base);
++
++ for (i = 0; data[i].mask || data[i].val; i++)
++ if (data[i].reg > regmap_config.max_register)
++ regmap_config.max_register = data[i].reg;
++
++ ephy->regmap = devm_regmap_init_mmio(dev, base, ®map_config);
++ if (IS_ERR(ephy->regmap))
++ return PTR_ERR(ephy->regmap);
++
++ phy = devm_phy_create(dev, dev->of_node, &en7528_pcie_phy_ops);
++ if (IS_ERR(phy))
++ return PTR_ERR(phy);
++
++ phy_set_drvdata(phy, ephy);
++
++ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
++
++ return PTR_ERR_OR_ZERO(provider);
++}
++
++static const struct of_device_id en7528_pcie_phy_ids[] = {
++ { .compatible = "econet,en7528-pcie-phy0", .data = en7528_phy_port0 },
++ { .compatible = "econet,en7528-pcie-phy1", .data = en7528_phy_port1 },
++ { .compatible = "econet,en751221-pcie-phy0", .data = en7528_phy_port0 },
++ { .compatible = "econet,en751221-pcie-phy1", .data = en751221_phy_port1 },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, en7528_pcie_phy_ids);
++
++static struct platform_driver en7528_pcie_phy_driver = {
++ .probe = en7528_pcie_phy_probe,
++ .driver = {
++ .name = "en7528-pcie-phy",
++ .of_match_table = en7528_pcie_phy_ids,
++ },
++};
++module_platform_driver(en7528_pcie_phy_driver);
++
++MODULE_AUTHOR("Ahmed Naseef <naseefkm@gmail.com>");
++MODULE_DESCRIPTION("EcoNet EN7528 PCIe PHY driver");
++MODULE_LICENSE("GPL v2");
--- /dev/null
+Subject: [PATCH] PCI: Skip bridge window reads when window is not supported
+
+pci_read_bridge_io() and pci_read_bridge_mmio_pref() read bridge window
+registers unconditionally. If the registers are hardwired to zero
+(not implemented), both base and limit will be 0. Since (0 <= 0) is
+true, a bogus window [mem 0x00000000-0x000fffff] or [io 0x0000-0x0fff]
+gets created.
+
+pci_read_bridge_windows() already detects unsupported windows by
+testing register writability and sets io_window/pref_window flags
+accordingly. Check these flags at the start of pci_read_bridge_io()
+and pci_read_bridge_mmio_pref() to skip reading registers when the
+window is not supported.
+
+Suggested-by: Bjorn Helgaas <helgaas@kernel.org>
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/pci/probe.c
++++ b/drivers/pci/probe.c
+@@ -349,6 +349,9 @@ static void pci_read_bridge_io(struct pc
+ unsigned long io_mask, io_granularity, base, limit;
+ struct pci_bus_region region;
+
++ if (!dev->io_window)
++ return;
++
+ io_mask = PCI_IO_RANGE_MASK;
+ io_granularity = 0x1000;
+ if (dev->io_window_1k) {
+@@ -410,6 +413,9 @@ static void pci_read_bridge_mmio_pref(st
+ pci_bus_addr_t base, limit;
+ struct pci_bus_region region;
+
++ if (!dev->pref_window)
++ return;
++
+ pci_read_config_word(dev, PCI_PREF_MEMORY_BASE, &mem_base_lo);
+ pci_read_config_word(dev, PCI_PREF_MEMORY_LIMIT, &mem_limit_lo);
+ base64 = (mem_base_lo & PCI_PREF_RANGE_MASK) << 16;
--- /dev/null
+From: Ahmed Naseef <naseefkm@gmail.com>
+Subject: phy: add EN7528 USB PHY driver
+
+Add USB PHY driver for EcoNet EN7528 SoC.
+
+Based on GPL vendor code at https://github.com/keenetic/kernel-49
+and the Airoha AN7581 USB PHY driver by Christian Marangi.
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/phy/Kconfig
++++ b/drivers/phy/Kconfig
+@@ -93,6 +93,17 @@ config PHY_EN7528_PCIE
+ This driver provides PHY initialization for the two PCIe ports
+ on EN7528 SoC.
+
++config PHY_EN7528_USB
++ tristate "EcoNet EN7528 USB PHY Driver"
++ depends on ECONET || COMPILE_TEST
++ depends on OF
++ select GENERIC_PHY
++ select REGMAP_MMIO
++ help
++ Say 'Y' here to add support for EcoNet EN7528 USB PHY driver.
++ This driver creates the basic PHY instance and provides
++ initialization callback for the USB port.
++
+ source "drivers/phy/allwinner/Kconfig"
+ source "drivers/phy/amlogic/Kconfig"
+ source "drivers/phy/broadcom/Kconfig"
+--- a/drivers/phy/Makefile
++++ b/drivers/phy/Makefile
+@@ -12,6 +12,7 @@ obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-
+ obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
+ obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
+ obj-$(CONFIG_PHY_EN7528_PCIE) += phy-en7528-pcie.o
++obj-$(CONFIG_PHY_EN7528_USB) += phy-en7528-usb.o
+ obj-y += allwinner/ \
+ amlogic/ \
+ broadcom/ \
+--- /dev/null
++++ b/drivers/phy/phy-en7528-usb.c
+@@ -0,0 +1,316 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * EcoNet EN7528 USB PHY driver
++ *
++ * Based on GPL vendor code at https://github.com/keenetic/kernel-49
++ * and the Airoha AN7581 USB PHY driver by
++ * Christian Marangi <ansuelsmth@gmail.com>
++ */
++
++#include <dt-bindings/phy/phy.h>
++#include <linux/bitfield.h>
++#include <linux/math.h>
++#include <linux/module.h>
++#include <linux/phy/phy.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++/* Frequency Meter registers (shared across ports) */
++#define EN7528_USB_PHY_FMCR0 0x100
++#define EN7528_USB_PHY_MONCLK_SEL GENMASK(27, 26)
++#define EN7528_USB_PHY_FREQDET_EN BIT(24)
++#define EN7528_USB_PHY_CYCLECNT GENMASK(23, 0)
++#define EN7528_USB_PHY_FMMONR0 0x10c
++#define EN7528_USB_PHY_FMMONR1 0x110
++#define EN7528_USB_PHY_FRCK_EN BIT(8)
++
++/* U2 PHY port bases */
++#define EN7528_USB_PHY_U2_P0_BASE 0x300
++#define EN7528_USB_PHY_U2_P1_BASE 0x1300
++#define EN7528_USB_PHY_NUM_U2_PORTS 2
++
++/* U2 PHY register offsets (relative to port base) */
++#define EN7528_USB_PHY_ACR0 0x10
++#define EN7528_USB_PHY_HSTX_SRCAL_EN BIT(23)
++#define EN7528_USB_PHY_HSTX_SRCTRL GENMASK(18, 16)
++#define EN7528_USB_PHY_ACR3 0x1c
++#define EN7528_USB_PHY_ACR3_ENABLE 0xC0240000
++
++/* U3 PHYA registers (base at +0xb00) */
++#define EN7528_USB_PHY_U3_PHYA_REG11 0xb2c
++#define EN7528_USB_PHY_RX_IMPSEL GENMASK(13, 12)
++
++#define EN7528_USB_PHY_FM_DET_CYCLE_CNT 1024
++#define EN7528_USB_PHY_REF_CK 20 /* MHz */
++#define EN7528_USB_PHY_SR_COEF 28
++#define EN7528_USB_PHY_SR_COEF_DIVISOR 1000
++#define EN7528_USB_PHY_DEFAULT_SR 4
++
++#define EN7528_USB_PHY_FREQDET_SLEEP 1000 /* 1ms */
++#define EN7528_USB_PHY_FREQDET_TIMEOUT (EN7528_USB_PHY_FREQDET_SLEEP * 10)
++
++static const unsigned int en7528_u2_port_bases[EN7528_USB_PHY_NUM_U2_PORTS] = {
++ EN7528_USB_PHY_U2_P0_BASE,
++ EN7528_USB_PHY_U2_P1_BASE,
++};
++
++struct en7528_usb_phy_instance {
++ struct phy *phy;
++ u32 type;
++};
++
++enum en7528_usb_phy_type {
++ EN7528_PHY_USB2,
++ EN7528_PHY_USB3,
++
++ EN7528_PHY_USB_MAX,
++};
++
++struct en7528_usb_phy_priv {
++ struct device *dev;
++ struct regmap *regmap;
++
++ struct en7528_usb_phy_instance *phys[EN7528_PHY_USB_MAX];
++};
++
++static void en7528_usb_phy_slew_rate_calibration(struct en7528_usb_phy_priv *priv,
++ unsigned int port_id,
++ unsigned int port_base)
++{
++ unsigned int acr0 = port_base + EN7528_USB_PHY_ACR0;
++ u32 fm_out;
++ u32 srctrl;
++
++ /* Enable HS TX SR calibration */
++ regmap_set_bits(priv->regmap, acr0,
++ EN7528_USB_PHY_HSTX_SRCAL_EN);
++
++ usleep_range(1000, 1500);
++
++ /* Enable free run clock */
++ regmap_set_bits(priv->regmap, EN7528_USB_PHY_FMMONR1,
++ EN7528_USB_PHY_FRCK_EN);
++
++ /* Select monitor clock: port 0 = MONCLK_SEL 0, port 1 = MONCLK_SEL 1 */
++ regmap_update_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
++ EN7528_USB_PHY_MONCLK_SEL,
++ FIELD_PREP(EN7528_USB_PHY_MONCLK_SEL, port_id));
++
++ /* Set cycle count */
++ regmap_update_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
++ EN7528_USB_PHY_CYCLECNT,
++ FIELD_PREP(EN7528_USB_PHY_CYCLECNT,
++ EN7528_USB_PHY_FM_DET_CYCLE_CNT));
++
++ /* Enable frequency meter */
++ regmap_set_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
++ EN7528_USB_PHY_FREQDET_EN);
++
++ /* Timeout can happen and we will apply default value at the end */
++ (void)regmap_read_poll_timeout(priv->regmap, EN7528_USB_PHY_FMMONR0,
++ fm_out, fm_out,
++ EN7528_USB_PHY_FREQDET_SLEEP,
++ EN7528_USB_PHY_FREQDET_TIMEOUT);
++
++ /* Disable frequency meter */
++ regmap_clear_bits(priv->regmap, EN7528_USB_PHY_FMCR0,
++ EN7528_USB_PHY_FREQDET_EN);
++
++ /* Disable free run clock */
++ regmap_clear_bits(priv->regmap, EN7528_USB_PHY_FMMONR1,
++ EN7528_USB_PHY_FRCK_EN);
++
++ /* Disable HS TX SR calibration */
++ regmap_clear_bits(priv->regmap, acr0,
++ EN7528_USB_PHY_HSTX_SRCAL_EN);
++
++ usleep_range(1000, 1500);
++
++ if (!fm_out) {
++ srctrl = EN7528_USB_PHY_DEFAULT_SR;
++ dev_err(priv->dev, "port%u: frequency not detected, using default SR calibration.\n",
++ port_id);
++ } else {
++ /* (1024 / FM_OUT) * REF_CK * SR_COEF */
++ srctrl = EN7528_USB_PHY_REF_CK * EN7528_USB_PHY_SR_COEF;
++ srctrl = (srctrl * EN7528_USB_PHY_FM_DET_CYCLE_CNT) / fm_out;
++ srctrl = DIV_ROUND_CLOSEST(srctrl, EN7528_USB_PHY_SR_COEF_DIVISOR);
++ dev_dbg(priv->dev, "port%u: SR calibration applied: %x\n",
++ port_id, srctrl);
++ }
++
++ regmap_update_bits(priv->regmap, acr0,
++ EN7528_USB_PHY_HSTX_SRCTRL,
++ FIELD_PREP(EN7528_USB_PHY_HSTX_SRCTRL, srctrl));
++}
++
++static int en7528_usb_phy_init(struct phy *phy)
++{
++ struct en7528_usb_phy_instance *instance = phy_get_drvdata(phy);
++ struct en7528_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
++ unsigned int i;
++
++ switch (instance->type) {
++ case PHY_TYPE_USB2:
++ /* Enable both U2 PHY ports before calibration */
++ for (i = 0; i < EN7528_USB_PHY_NUM_U2_PORTS; i++)
++ regmap_write(priv->regmap,
++ en7528_u2_port_bases[i] + EN7528_USB_PHY_ACR3,
++ EN7528_USB_PHY_ACR3_ENABLE);
++ break;
++ case PHY_TYPE_USB3:
++ /* Combo PHY Rx R mean value too high, tune -5 Ohm */
++ regmap_update_bits(priv->regmap,
++ EN7528_USB_PHY_U3_PHYA_REG11,
++ EN7528_USB_PHY_RX_IMPSEL,
++ FIELD_PREP(EN7528_USB_PHY_RX_IMPSEL, 0x1));
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int en7528_usb_phy_power_on(struct phy *phy)
++{
++ struct en7528_usb_phy_instance *instance = phy_get_drvdata(phy);
++ struct en7528_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
++ unsigned int i;
++
++ if (instance->type != PHY_TYPE_USB2)
++ return 0;
++
++ /* Calibrate slew rate for both U2 PHY ports */
++ for (i = 0; i < EN7528_USB_PHY_NUM_U2_PORTS; i++)
++ en7528_usb_phy_slew_rate_calibration(priv, i,
++ en7528_u2_port_bases[i]);
++
++ return 0;
++}
++
++static const struct phy_ops en7528_usb_phy_ops = {
++ .init = en7528_usb_phy_init,
++ .power_on = en7528_usb_phy_power_on,
++ .owner = THIS_MODULE,
++};
++
++static struct phy *en7528_usb_phy_xlate(struct device *dev,
++ const struct of_phandle_args *args)
++{
++ struct en7528_usb_phy_priv *priv = dev_get_drvdata(dev);
++ struct en7528_usb_phy_instance *instance = NULL;
++ unsigned int index, phy_type;
++
++ if (args->args_count != 1) {
++ dev_err(dev, "invalid number of cells in 'phy' property\n");
++ return ERR_PTR(-EINVAL);
++ }
++
++ phy_type = args->args[0];
++ if (!(phy_type == PHY_TYPE_USB2 || phy_type == PHY_TYPE_USB3)) {
++ dev_err(dev, "unsupported device type: %d\n", phy_type);
++ return ERR_PTR(-EINVAL);
++ }
++
++ for (index = 0; index < EN7528_PHY_USB_MAX; index++)
++ if (priv->phys[index] &&
++ phy_type == priv->phys[index]->type) {
++ instance = priv->phys[index];
++ break;
++ }
++
++ if (!instance) {
++ dev_err(dev, "failed to find appropriate phy\n");
++ return ERR_PTR(-EINVAL);
++ }
++
++ return instance->phy;
++}
++
++static const struct regmap_config en7528_usb_phy_regmap_config = {
++ .reg_bits = 32,
++ .val_bits = 32,
++ .reg_stride = 4,
++ .max_register = EN7528_USB_PHY_U2_P1_BASE + EN7528_USB_PHY_ACR3,
++};
++
++static int en7528_usb_phy_probe(struct platform_device *pdev)
++{
++ struct phy_provider *phy_provider;
++ struct en7528_usb_phy_priv *priv;
++ struct device *dev = &pdev->dev;
++ unsigned int index;
++ void *base;
++
++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ priv->dev = dev;
++
++ base = devm_platform_ioremap_resource(pdev, 0);
++ if (IS_ERR(base))
++ return PTR_ERR(base);
++
++ priv->regmap = devm_regmap_init_mmio(dev, base,
++ &en7528_usb_phy_regmap_config);
++ if (IS_ERR(priv->regmap))
++ return PTR_ERR(priv->regmap);
++
++ platform_set_drvdata(pdev, priv);
++
++ for (index = 0; index < EN7528_PHY_USB_MAX; index++) {
++ struct en7528_usb_phy_instance *instance;
++ enum en7528_usb_phy_type phy_type;
++
++ switch (index) {
++ case EN7528_PHY_USB2:
++ phy_type = PHY_TYPE_USB2;
++ break;
++ case EN7528_PHY_USB3:
++ phy_type = PHY_TYPE_USB3;
++ break;
++ default:
++ continue;
++ }
++
++ instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
++ if (!instance)
++ return -ENOMEM;
++
++ instance->type = phy_type;
++ priv->phys[index] = instance;
++
++ instance->phy = devm_phy_create(dev, NULL, &en7528_usb_phy_ops);
++ if (IS_ERR(instance->phy))
++ return dev_err_probe(dev, PTR_ERR(instance->phy),
++ "failed to create phy\n");
++
++ phy_set_drvdata(instance->phy, instance);
++ }
++
++ phy_provider = devm_of_phy_provider_register(dev,
++ en7528_usb_phy_xlate);
++
++ return PTR_ERR_OR_ZERO(phy_provider);
++}
++
++static const struct of_device_id en7528_usb_phy_match[] = {
++ { .compatible = "econet,en7528-usb-phy" },
++ { },
++};
++MODULE_DEVICE_TABLE(of, en7528_usb_phy_match);
++
++static struct platform_driver en7528_usb_phy_driver = {
++ .probe = en7528_usb_phy_probe,
++ .driver = {
++ .name = "en7528-usb-phy",
++ .of_match_table = en7528_usb_phy_match,
++ },
++};
++
++module_platform_driver(en7528_usb_phy_driver);
++
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("EcoNet EN7528 USB PHY driver");
--- /dev/null
+From: Ahmed Naseef <naseefkm@gmail.com>
+Subject: usb: xhci-mtk: add EN7528 LTSSM timing quirk
+
+EN7528 needs LTSSM Timing Parameter 5 configured to fix
+TD 6.5 compliance test failures.
+
+Based on GPL vendor code:
+https://github.com/keenetic/kernel-49/commit/ee0e0b7cf28c208cd5b892ea96180ffae0de9b7f
+
+Signed-off-by: Ahmed Naseef <naseefkm@gmail.com>
+--- a/drivers/usb/host/xhci-mtk.c
++++ b/drivers/usb/host/xhci-mtk.c
+@@ -80,6 +80,8 @@
+ #define SS_GEN2_EOF_CFG 0x990
+ #define SSG2EOF_OFFSET 0x3c
+
++#define XHCI_MTK_LTSSM_TIMING_PARAMETER5 0x251c
++
+ #define XSEOF_OFFSET_MASK GENMASK(11, 0)
+
+ /* usb remote wakeup registers in syscon */
+@@ -191,6 +193,18 @@ static void xhci_mtk_rxfifo_depth_set(st
+ writel(value, hcd->regs + HSCH_CFG1);
+ }
+
++/* EN7528: fix TD 6.5 compliance test failure */
++static void xhci_mtk_ltssm_quirk(struct xhci_hcd_mtk *mtk)
++{
++ struct device *dev = mtk->dev;
++ struct usb_hcd *hcd = mtk->hcd;
++
++ if (!of_device_is_compatible(dev->of_node, "econet,en7528-xhci"))
++ return;
++
++ writel(0x203e8, hcd->regs + XHCI_MTK_LTSSM_TIMING_PARAMETER5);
++}
++
+ static void xhci_mtk_init_quirk(struct xhci_hcd_mtk *mtk)
+ {
+ /* workaround only for mt8195 */
+@@ -198,6 +212,9 @@ static void xhci_mtk_init_quirk(struct x
+
+ /* workaround for SoCs using SSUSB about before IPM v1.6.0 */
+ xhci_mtk_rxfifo_depth_set(mtk);
++
++ /* EN7528 LTSSM timing fix */
++ xhci_mtk_ltssm_quirk(mtk);
+ }
+
+ static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
+@@ -846,6 +863,7 @@ static const struct dev_pm_ops xhci_mtk_
+ static const struct of_device_id mtk_xhci_of_match[] = {
+ { .compatible = "mediatek,mt8173-xhci"},
+ { .compatible = "mediatek,mt8195-xhci"},
++ { .compatible = "econet,en7528-xhci"},
+ { .compatible = "mediatek,mtk-xhci"},
+ { },
+ };