summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/dts/an7581-u-boot.dtsi90
-rw-r--r--configs/an7581_evb_defconfig3
-rw-r--r--drivers/mmc/Kconfig2
-rw-r--r--drivers/mtd/nand/spi/core.c18
-rw-r--r--drivers/net/Kconfig8
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/airoha_eth.c948
-rw-r--r--drivers/spi/Kconfig9
-rw-r--r--drivers/spi/Makefile1
-rw-r--r--drivers/spi/airoha_snfi_spi.c718
-rw-r--r--include/linux/mtd/spinand.h2
-rw-r--r--include/regmap.h28
-rw-r--r--include/spi.h12
13 files changed, 1838 insertions, 2 deletions
diff --git a/arch/arm/dts/an7581-u-boot.dtsi b/arch/arm/dts/an7581-u-boot.dtsi
index 0316b73f3a5..a9297ca6503 100644
--- a/arch/arm/dts/an7581-u-boot.dtsi
+++ b/arch/arm/dts/an7581-u-boot.dtsi
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
+#include <dt-bindings/reset/airoha,en7581-reset.h>
+
/ {
reserved-memory {
#address-cells = <2>;
@@ -11,6 +13,94 @@
reg = <0x0 0x80000000 0x0 0x40000>;
};
};
+
+ clk25m: oscillator {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <25000000>;
+ clock-output-names = "clkxtal";
+ };
+
+ vmmc_3v3: regulator-vmmc-3v3 {
+ compatible = "regulator-fixed";
+ regulator-name = "vmmc";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-always-on;
+ };
+
+ soc {
+ chip_scu: syscon@1fa20000 {
+ compatible = "airoha,en7581-chip-scu", "syscon";
+ reg = <0x0 0x1fa20000 0x0 0x388>;
+ };
+
+ eth: ethernet@1fb50000 {
+ compatible = "airoha,en7581-eth";
+ reg = <0 0x1fb50000 0 0x2600>,
+ <0 0x1fb54000 0 0x2000>,
+ <0 0x1fb56000 0 0x2000>;
+ reg-names = "fe", "qdma0", "qdma1";
+
+ resets = <&scuclk EN7581_FE_RST>,
+ <&scuclk EN7581_FE_PDMA_RST>,
+ <&scuclk EN7581_FE_QDMA_RST>,
+ <&scuclk EN7581_DUAL_HSI0_MAC_RST>,
+ <&scuclk EN7581_DUAL_HSI1_MAC_RST>,
+ <&scuclk EN7581_HSI_MAC_RST>,
+ <&scuclk EN7581_XFP_MAC_RST>;
+ reset-names = "fe", "pdma", "qdma",
+ "hsi0-mac", "hsi1-mac", "hsi-mac",
+ "xfp-mac";
+ };
+
+ switch: switch@1fb58000 {
+ compatible = "airoha,en7581-switch";
+ reg = <0 0x1fb58000 0 0x8000>;
+ };
+
+ snfi: spi@1fa10000 {
+ compatible = "airoha,en7581-snand";
+ reg = <0x0 0x1fa10000 0x0 0x140>,
+ <0x0 0x1fa11000 0x0 0x600>;
+
+ clocks = <&scuclk EN7523_CLK_SPI>;
+ clock-names = "spi";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ spi_nand: nand@0 {
+ compatible = "spi-nand";
+ reg = <0>;
+ spi-max-frequency = <50000000>;
+ spi-tx-bus-width = <1>;
+ spi-rx-bus-width = <2>;
+ };
+ };
+
+ mmc0: mmc@1fa0e000 {
+ compatible = "mediatek,mt7622-mmc";
+ reg = <0x0 0x1fa0e000 0x0 0x1000>,
+ <0x0 0x1fa0c000 0x0 0x60>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&scuclk EN7581_CLK_EMMC>, <&clk25m>;
+ clock-names = "source", "hclk";
+ bus-width = <4>;
+ max-frequency = <52000000>;
+ vmmc-supply = <&vmmc_3v3>;
+ disable-wp;
+ cap-mmc-highspeed;
+ non-removable;
+
+ assigned-clocks = <&scuclk EN7581_CLK_EMMC>;
+ assigned-clock-rates = <200000000>;
+ };
+ };
+};
+
+&scuclk {
+ compatible = "airoha,en7581-scu", "syscon";
};
&uart1 {
diff --git a/configs/an7581_evb_defconfig b/configs/an7581_evb_defconfig
index f09b5b603a2..c74247e13db 100644
--- a/configs/an7581_evb_defconfig
+++ b/configs/an7581_evb_defconfig
@@ -76,3 +76,6 @@ CONFIG_SYS_NS16550=y
CONFIG_SPI=y
CONFIG_DM_SPI=y
CONFIG_SHA512=y
+CONFIG_AIROHA_ETH=y
+CONFIG_MMC_MTK=y
+CONFIG_AIROHA_SNFI_SPI=y
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index 3ea665d974d..38867f30a7e 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -871,7 +871,7 @@ config FTSDC010_SDIO
config MMC_MTK
bool "MediaTek SD/MMC Card Interface support"
- depends on ARCH_MEDIATEK || ARCH_MTMIPS
+ depends on ARCH_MEDIATEK || ARCH_MTMIPS || ARCH_AIROHA
depends on OF_CONTROL
help
This selects the MediaTek(R) Secure digital and Multimedia card Interface.
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index f5ddfbf4b83..3a1e7e18736 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -941,6 +941,19 @@ spinand_select_op_variant(struct spinand_device *spinand,
return NULL;
}
+static int spinand_setup_slave(struct spinand_device *spinand,
+ const struct spinand_info *spinand_info)
+{
+ struct spi_slave *slave = spinand->slave;
+ struct udevice *bus = slave->dev->parent;
+ struct dm_spi_ops *ops = spi_get_ops(bus);
+
+ if (!ops->setup_for_spinand)
+ return 0;
+
+ return ops->setup_for_spinand(slave, spinand_info);
+}
+
/**
* spinand_match_and_init() - Try to find a match between a device ID and an
* entry in a spinand_info table
@@ -964,6 +977,7 @@ int spinand_match_and_init(struct spinand_device *spinand,
u8 *id = spinand->id.data;
struct nand_device *nand = spinand_to_nand(spinand);
unsigned int i;
+ int ret;
for (i = 0; i < table_size; i++) {
const struct spinand_info *info = &table[i];
@@ -975,6 +989,10 @@ int spinand_match_and_init(struct spinand_device *spinand,
if (memcmp(id + 1, info->devid.id, info->devid.len))
continue;
+ ret = spinand_setup_slave(spinand, info);
+ if (ret)
+ return ret;
+
nand->memorg = table[i].memorg;
nand->eccreq = table[i].eccreq;
spinand->eccinfo = table[i].eccinfo;
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 3db784faedd..a0a7890bd26 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -122,6 +122,14 @@ config AG7XXX
This driver supports the Atheros AG7xxx Ethernet MAC. This MAC is
present in the Atheros AR7xxx, AR9xxx and QCA9xxx MIPS chips.
+config AIROHA_ETH
+ bool "Airoha Ethernet QDMA Driver"
+ depends on ARCH_AIROHA
+ select PHYLIB
+ select DM_RESET
+ help
+ This Driver support Airoha Ethernet QDMA Driver
+ Say Y to enable support for the Airoha Ethernet QDMA.
config ALTERA_TSE
bool "Altera Triple-Speed Ethernet MAC support"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index d919d437c08..3244d39036d 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -5,6 +5,7 @@
obj-$(CONFIG_AG7XXX) += ag7xxx.o
+obj-$(CONFIG_AIROHA_ETH) += airoha_eth.o
obj-$(CONFIG_ALTERA_TSE) += altera_tse.o
obj-$(CONFIG_ASPEED_MDIO) += aspeed_mdio.o
obj-$(CONFIG_BCM6348_ETH) += bcm6348-eth.o
diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c
new file mode 100644
index 00000000000..7e35e1fd41d
--- /dev/null
+++ b/drivers/net/airoha_eth.c
@@ -0,0 +1,948 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Based on Linux airoha_eth.c majorly rewritten
+ * and simplified for U-Boot usage for single TX/RX ring.
+ *
+ * Copyright (c) 2024 AIROHA Inc
+ * Author: Lorenzo Bianconi <lorenzo@kernel.org>
+ * Christian Marangi <ansuelsmth@gmail.org>
+ */
+
+#include <dm.h>
+#include <dm/devres.h>
+#include <mapmem.h>
+#include <net.h>
+#include <regmap.h>
+#include <reset.h>
+#include <syscon.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/time.h>
+
+#define AIROHA_MAX_NUM_GDM_PORTS 1
+#define AIROHA_MAX_NUM_QDMA 1
+#define AIROHA_MAX_NUM_RSTS 3
+#define AIROHA_MAX_NUM_XSI_RSTS 4
+
+#define AIROHA_MAX_PACKET_SIZE 2048
+#define AIROHA_NUM_TX_RING 1
+#define AIROHA_NUM_RX_RING 1
+#define AIROHA_NUM_TX_IRQ 1
+#define HW_DSCP_NUM 32
+#define IRQ_QUEUE_LEN 1
+#define TX_DSCP_NUM 16
+#define RX_DSCP_NUM PKTBUFSRX
+
+/* SCU */
+#define SCU_SHARE_FEMEM_SEL 0x958
+
+/* SWITCH */
+#define SWITCH_MFC 0x10
+#define SWITCH_BC_FFP GENMASK(31, 24)
+#define SWITCH_UNM_FFP GENMASK(23, 16)
+#define SWITCH_UNU_FFP GENMASK(15, 8)
+#define SWITCH_PMCR(_n) 0x3000 + ((_n) * 0x100)
+#define SWITCH_IPG_CFG GENMASK(19, 18)
+#define SWITCH_IPG_CFG_NORMAL FIELD_PREP(SWITCH_IPG_CFG, 0x0)
+#define SWITCH_IPG_CFG_SHORT FIELD_PREP(SWITCH_IPG_CFG, 0x1)
+#define SWITCH_IPG_CFG_SHRINK FIELD_PREP(SWITCH_IPG_CFG, 0x2)
+#define SWITCH_MAC_MODE BIT(16)
+#define SWITCH_FORCE_MODE BIT(15)
+#define SWITCH_MAC_TX_EN BIT(14)
+#define SWITCH_MAC_RX_EN BIT(13)
+#define SWITCH_BKOFF_EN BIT(9)
+#define SWITCH_BKPR_EN BIT(8)
+#define SWITCH_FORCE_RX_FC BIT(5)
+#define SWITCH_FORCE_TX_FC BIT(4)
+#define SWITCH_FORCE_SPD GENMASK(3, 2)
+#define SWITCH_FORCE_SPD_10 FIELD_PREP(SWITCH_FORCE_SPD, 0x0)
+#define SWITCH_FORCE_SPD_100 FIELD_PREP(SWITCH_FORCE_SPD, 0x1)
+#define SWITCH_FORCE_SPD_1000 FIELD_PREP(SWITCH_FORCE_SPD, 0x2)
+#define SWITCH_FORCE_DPX BIT(1)
+#define SWITCH_FORCE_LNK BIT(0)
+#define SWITCH_SMACCR0 0x30e4
+#define SMACCR0_MAC2 GENMASK(31, 24)
+#define SMACCR0_MAC3 GENMASK(23, 16)
+#define SMACCR0_MAC4 GENMASK(15, 8)
+#define SMACCR0_MAC5 GENMASK(7, 0)
+#define SWITCH_SMACCR1 0x30e8
+#define SMACCR1_MAC0 GENMASK(15, 8)
+#define SMACCR1_MAC1 GENMASK(7, 0)
+#define SWITCH_PHY_POLL 0x7018
+#define SWITCH_PHY_AP_EN GENMASK(30, 24)
+#define SWITCH_EEE_POLL_EN GENMASK(22, 16)
+#define SWITCH_PHY_PRE_EN BIT(15)
+#define SWITCH_PHY_END_ADDR GENMASK(12, 8)
+#define SWITCH_PHY_ST_ADDR GENMASK(4, 0)
+
+/* FE */
+#define PSE_BASE 0x0100
+#define CSR_IFC_BASE 0x0200
+#define CDM1_BASE 0x0400
+#define GDM1_BASE 0x0500
+#define PPE1_BASE 0x0c00
+
+#define CDM2_BASE 0x1400
+#define GDM2_BASE 0x1500
+
+#define GDM3_BASE 0x1100
+#define GDM4_BASE 0x2500
+
+#define GDM_BASE(_n) \
+ ((_n) == 4 ? GDM4_BASE : \
+ (_n) == 3 ? GDM3_BASE : \
+ (_n) == 2 ? GDM2_BASE : GDM1_BASE)
+
+#define REG_GDM_FWD_CFG(_n) GDM_BASE(_n)
+#define GDM_DROP_CRC_ERR BIT(23)
+#define GDM_IP4_CKSUM BIT(22)
+#define GDM_TCP_CKSUM BIT(21)
+#define GDM_UDP_CKSUM BIT(20)
+#define GDM_UCFQ_MASK GENMASK(15, 12)
+#define GDM_BCFQ_MASK GENMASK(11, 8)
+#define GDM_MCFQ_MASK GENMASK(7, 4)
+#define GDM_OCFQ_MASK GENMASK(3, 0)
+
+/* QDMA */
+#define REG_QDMA_GLOBAL_CFG 0x0004
+#define GLOBAL_CFG_RX_2B_OFFSET_MASK BIT(31)
+#define GLOBAL_CFG_DMA_PREFERENCE_MASK GENMASK(30, 29)
+#define GLOBAL_CFG_CPU_TXR_RR_MASK BIT(28)
+#define GLOBAL_CFG_DSCP_BYTE_SWAP_MASK BIT(27)
+#define GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK BIT(26)
+#define GLOBAL_CFG_MULTICAST_MODIFY_FP_MASK BIT(25)
+#define GLOBAL_CFG_OAM_MODIFY_MASK BIT(24)
+#define GLOBAL_CFG_RESET_MASK BIT(23)
+#define GLOBAL_CFG_RESET_DONE_MASK BIT(22)
+#define GLOBAL_CFG_MULTICAST_EN_MASK BIT(21)
+#define GLOBAL_CFG_IRQ1_EN_MASK BIT(20)
+#define GLOBAL_CFG_IRQ0_EN_MASK BIT(19)
+#define GLOBAL_CFG_LOOPCNT_EN_MASK BIT(18)
+#define GLOBAL_CFG_RD_BYPASS_WR_MASK BIT(17)
+#define GLOBAL_CFG_QDMA_LOOPBACK_MASK BIT(16)
+#define GLOBAL_CFG_LPBK_RXQ_SEL_MASK GENMASK(13, 8)
+#define GLOBAL_CFG_CHECK_DONE_MASK BIT(7)
+#define GLOBAL_CFG_TX_WB_DONE_MASK BIT(6)
+#define GLOBAL_CFG_MAX_ISSUE_NUM_MASK GENMASK(5, 4)
+#define GLOBAL_CFG_RX_DMA_BUSY_MASK BIT(3)
+#define GLOBAL_CFG_RX_DMA_EN_MASK BIT(2)
+#define GLOBAL_CFG_TX_DMA_BUSY_MASK BIT(1)
+#define GLOBAL_CFG_TX_DMA_EN_MASK BIT(0)
+
+#define REG_FWD_DSCP_BASE 0x0010
+#define REG_FWD_BUF_BASE 0x0014
+
+#define REG_HW_FWD_DSCP_CFG 0x0018
+#define HW_FWD_DSCP_PAYLOAD_SIZE_MASK GENMASK(29, 28)
+#define HW_FWD_DSCP_SCATTER_LEN_MASK GENMASK(17, 16)
+#define HW_FWD_DSCP_MIN_SCATTER_LEN_MASK GENMASK(15, 0)
+
+#define REG_INT_STATUS(_n) \
+ (((_n) == 4) ? 0x0730 : \
+ ((_n) == 3) ? 0x0724 : \
+ ((_n) == 2) ? 0x0720 : \
+ ((_n) == 1) ? 0x0024 : 0x0020)
+
+#define REG_TX_IRQ_BASE(_n) ((_n) ? 0x0048 : 0x0050)
+
+#define REG_TX_IRQ_CFG(_n) ((_n) ? 0x004c : 0x0054)
+#define TX_IRQ_THR_MASK GENMASK(27, 16)
+#define TX_IRQ_DEPTH_MASK GENMASK(11, 0)
+
+#define REG_IRQ_CLEAR_LEN(_n) ((_n) ? 0x0064 : 0x0058)
+#define IRQ_CLEAR_LEN_MASK GENMASK(7, 0)
+
+#define REG_TX_RING_BASE(_n) \
+ (((_n) < 8) ? 0x0100 + ((_n) << 5) : 0x0b00 + (((_n) - 8) << 5))
+
+#define REG_TX_CPU_IDX(_n) \
+ (((_n) < 8) ? 0x0108 + ((_n) << 5) : 0x0b08 + (((_n) - 8) << 5))
+
+#define TX_RING_CPU_IDX_MASK GENMASK(15, 0)
+
+#define REG_TX_DMA_IDX(_n) \
+ (((_n) < 8) ? 0x010c + ((_n) << 5) : 0x0b0c + (((_n) - 8) << 5))
+
+#define TX_RING_DMA_IDX_MASK GENMASK(15, 0)
+
+#define IRQ_RING_IDX_MASK GENMASK(20, 16)
+#define IRQ_DESC_IDX_MASK GENMASK(15, 0)
+
+#define REG_RX_RING_BASE(_n) \
+ (((_n) < 16) ? 0x0200 + ((_n) << 5) : 0x0e00 + (((_n) - 16) << 5))
+
+#define REG_RX_RING_SIZE(_n) \
+ (((_n) < 16) ? 0x0204 + ((_n) << 5) : 0x0e04 + (((_n) - 16) << 5))
+
+#define RX_RING_THR_MASK GENMASK(31, 16)
+#define RX_RING_SIZE_MASK GENMASK(15, 0)
+
+#define REG_RX_CPU_IDX(_n) \
+ (((_n) < 16) ? 0x0208 + ((_n) << 5) : 0x0e08 + (((_n) - 16) << 5))
+
+#define RX_RING_CPU_IDX_MASK GENMASK(15, 0)
+
+#define REG_RX_DMA_IDX(_n) \
+ (((_n) < 16) ? 0x020c + ((_n) << 5) : 0x0e0c + (((_n) - 16) << 5))
+
+#define REG_RX_DELAY_INT_IDX(_n) \
+ (((_n) < 16) ? 0x0210 + ((_n) << 5) : 0x0e10 + (((_n) - 16) << 5))
+
+#define RX_DELAY_INT_MASK GENMASK(15, 0)
+
+#define RX_RING_DMA_IDX_MASK GENMASK(15, 0)
+
+#define REG_LMGR_INIT_CFG 0x1000
+#define LMGR_INIT_START BIT(31)
+#define LMGR_SRAM_MODE_MASK BIT(30)
+#define HW_FWD_PKTSIZE_OVERHEAD_MASK GENMASK(27, 20)
+#define HW_FWD_DESC_NUM_MASK GENMASK(16, 0)
+
+/* CTRL */
+#define QDMA_DESC_DONE_MASK BIT(31)
+#define QDMA_DESC_DROP_MASK BIT(30) /* tx: drop - rx: overflow */
+#define QDMA_DESC_MORE_MASK BIT(29) /* more SG elements */
+#define QDMA_DESC_DEI_MASK BIT(25)
+#define QDMA_DESC_NO_DROP_MASK BIT(24)
+#define QDMA_DESC_LEN_MASK GENMASK(15, 0)
+/* DATA */
+#define QDMA_DESC_NEXT_ID_MASK GENMASK(15, 0)
+/* TX MSG0 */
+#define QDMA_ETH_TXMSG_MIC_IDX_MASK BIT(30)
+#define QDMA_ETH_TXMSG_SP_TAG_MASK GENMASK(29, 14)
+#define QDMA_ETH_TXMSG_ICO_MASK BIT(13)
+#define QDMA_ETH_TXMSG_UCO_MASK BIT(12)
+#define QDMA_ETH_TXMSG_TCO_MASK BIT(11)
+#define QDMA_ETH_TXMSG_TSO_MASK BIT(10)
+#define QDMA_ETH_TXMSG_FAST_MASK BIT(9)
+#define QDMA_ETH_TXMSG_OAM_MASK BIT(8)
+#define QDMA_ETH_TXMSG_CHAN_MASK GENMASK(7, 3)
+#define QDMA_ETH_TXMSG_QUEUE_MASK GENMASK(2, 0)
+/* TX MSG1 */
+#define QDMA_ETH_TXMSG_NO_DROP BIT(31)
+#define QDMA_ETH_TXMSG_METER_MASK GENMASK(30, 24) /* 0x7f no meters */
+#define QDMA_ETH_TXMSG_FPORT_MASK GENMASK(23, 20)
+#define QDMA_ETH_TXMSG_NBOQ_MASK GENMASK(19, 15)
+#define QDMA_ETH_TXMSG_HWF_MASK BIT(14)
+#define QDMA_ETH_TXMSG_HOP_MASK BIT(13)
+#define QDMA_ETH_TXMSG_PTP_MASK BIT(12)
+#define QDMA_ETH_TXMSG_ACNT_G1_MASK GENMASK(10, 6) /* 0x1f do not count */
+#define QDMA_ETH_TXMSG_ACNT_G0_MASK GENMASK(5, 0) /* 0x3f do not count */
+
+/* RX MSG1 */
+#define QDMA_ETH_RXMSG_DEI_MASK BIT(31)
+#define QDMA_ETH_RXMSG_IP6_MASK BIT(30)
+#define QDMA_ETH_RXMSG_IP4_MASK BIT(29)
+#define QDMA_ETH_RXMSG_IP4F_MASK BIT(28)
+#define QDMA_ETH_RXMSG_L4_VALID_MASK BIT(27)
+#define QDMA_ETH_RXMSG_L4F_MASK BIT(26)
+#define QDMA_ETH_RXMSG_SPORT_MASK GENMASK(25, 21)
+#define QDMA_ETH_RXMSG_CRSN_MASK GENMASK(20, 16)
+#define QDMA_ETH_RXMSG_PPE_ENTRY_MASK GENMASK(15, 0)
+
+struct airoha_qdma_desc {
+ __le32 rsv;
+ __le32 ctrl;
+ __le32 addr;
+ __le32 data;
+ __le32 msg0;
+ __le32 msg1;
+ __le32 msg2;
+ __le32 msg3;
+};
+
+struct airoha_qdma_fwd_desc {
+ __le32 addr;
+ __le32 ctrl0;
+ __le32 ctrl1;
+ __le32 ctrl2;
+ __le32 msg0;
+ __le32 msg1;
+ __le32 rsv0;
+ __le32 rsv1;
+};
+
+struct airoha_queue {
+ struct airoha_qdma_desc *desc;
+ u16 head;
+
+ int ndesc;
+};
+
+struct airoha_tx_irq_queue {
+ struct airoha_qdma *qdma;
+
+ int size;
+ u32 *q;
+};
+
+struct airoha_qdma {
+ struct airoha_eth *eth;
+ void __iomem *regs;
+
+ struct airoha_tx_irq_queue q_tx_irq[AIROHA_NUM_TX_IRQ];
+
+ struct airoha_queue q_tx[AIROHA_NUM_TX_RING];
+ struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
+
+ /* descriptor and packet buffers for qdma hw forward */
+ struct {
+ void *desc;
+ void *q;
+ } hfwd;
+};
+
+struct airoha_gdm_port {
+ struct airoha_qdma *qdma;
+ int id;
+};
+
+struct airoha_eth {
+ void __iomem *fe_regs;
+ void __iomem *switch_regs;
+
+ struct reset_ctl_bulk rsts;
+ struct reset_ctl_bulk xsi_rsts;
+
+ struct airoha_qdma qdma[AIROHA_MAX_NUM_QDMA];
+ struct airoha_gdm_port *ports[AIROHA_MAX_NUM_GDM_PORTS];
+};
+
+static u32 airoha_rr(void __iomem *base, u32 offset)
+{
+ return readl(base + offset);
+}
+
+static void airoha_wr(void __iomem *base, u32 offset, u32 val)
+{
+ writel(val, base + offset);
+}
+
+static u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
+{
+ val |= (airoha_rr(base, offset) & ~mask);
+ airoha_wr(base, offset, val);
+
+ return val;
+}
+
+#define airoha_fe_rr(eth, offset) \
+ airoha_rr((eth)->fe_regs, (offset))
+#define airoha_fe_wr(eth, offset, val) \
+ airoha_wr((eth)->fe_regs, (offset), (val))
+#define airoha_fe_rmw(eth, offset, mask, val) \
+ airoha_rmw((eth)->fe_regs, (offset), (mask), (val))
+#define airoha_fe_set(eth, offset, val) \
+ airoha_rmw((eth)->fe_regs, (offset), 0, (val))
+#define airoha_fe_clear(eth, offset, val) \
+ airoha_rmw((eth)->fe_regs, (offset), (val), 0)
+
+#define airoha_qdma_rr(qdma, offset) \
+ airoha_rr((qdma)->regs, (offset))
+#define airoha_qdma_wr(qdma, offset, val) \
+ airoha_wr((qdma)->regs, (offset), (val))
+#define airoha_qdma_rmw(qdma, offset, mask, val) \
+ airoha_rmw((qdma)->regs, (offset), (mask), (val))
+#define airoha_qdma_set(qdma, offset, val) \
+ airoha_rmw((qdma)->regs, (offset), 0, (val))
+#define airoha_qdma_clear(qdma, offset, val) \
+ airoha_rmw((qdma)->regs, (offset), (val), 0)
+
+#define airoha_switch_wr(eth, offset, val) \
+ airoha_wr((eth)->switch_regs, (offset), (val))
+
+static void airoha_fe_maccr_init(struct airoha_eth *eth)
+{
+ int p;
+
+ for (p = 1; p <= ARRAY_SIZE(eth->ports); p++) {
+ /* Disable any kind of CRC drop or offload */
+ airoha_fe_wr(eth, REG_GDM_FWD_CFG(p), 0);
+ }
+}
+
+static int airoha_fe_init(struct airoha_eth *eth)
+{
+ airoha_fe_maccr_init(eth);
+
+ return 0;
+}
+
+static void airoha_qdma_reset_rx_desc(struct airoha_queue *q, int index,
+ uchar *rx_packet)
+{
+ struct airoha_qdma_desc *desc;
+ u32 val;
+
+ desc = &q->desc[index];
+ index = (index + 1) % q->ndesc;
+
+ dma_map_single(rx_packet, PKTSIZE_ALIGN, DMA_TO_DEVICE);
+
+ WRITE_ONCE(desc->msg0, cpu_to_le32(0));
+ WRITE_ONCE(desc->msg1, cpu_to_le32(0));
+ WRITE_ONCE(desc->msg2, cpu_to_le32(0));
+ WRITE_ONCE(desc->msg3, cpu_to_le32(0));
+ WRITE_ONCE(desc->addr, cpu_to_le32(virt_to_phys(rx_packet)));
+ WRITE_ONCE(desc->data, cpu_to_le32(index));
+ val = FIELD_PREP(QDMA_DESC_LEN_MASK, PKTSIZE_ALIGN);
+ WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
+
+ dma_map_single(desc, sizeof(*desc), DMA_TO_DEVICE);
+}
+
+static void airoha_qdma_init_rx_desc(struct airoha_queue *q)
+{
+ int i;
+
+ for (i = 0; i < q->ndesc; i++)
+ airoha_qdma_reset_rx_desc(q, i, net_rx_packets[i]);
+}
+
+static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
+ struct airoha_qdma *qdma, int ndesc)
+{
+ int qid = q - &qdma->q_rx[0];
+ unsigned long dma_addr;
+
+ q->ndesc = ndesc;
+ q->head = 0;
+
+ q->desc = dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+ if (!q->desc)
+ return -ENOMEM;
+
+ memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+ dma_map_single(q->desc, q->ndesc * sizeof(*q->desc), DMA_TO_DEVICE);
+
+ airoha_qdma_wr(qdma, REG_RX_RING_BASE(qid), dma_addr);
+ airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid),
+ RX_RING_SIZE_MASK,
+ FIELD_PREP(RX_RING_SIZE_MASK, ndesc));
+
+ airoha_qdma_rmw(qdma, REG_RX_RING_SIZE(qid), RX_RING_THR_MASK,
+ FIELD_PREP(RX_RING_THR_MASK, 0));
+ airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid), RX_RING_CPU_IDX_MASK,
+ FIELD_PREP(RX_RING_CPU_IDX_MASK, q->ndesc - 1));
+ airoha_qdma_rmw(qdma, REG_RX_DMA_IDX(qid), RX_RING_DMA_IDX_MASK,
+ FIELD_PREP(RX_RING_DMA_IDX_MASK, q->head));
+
+ return 0;
+}
+
+static int airoha_qdma_init_rx(struct airoha_qdma *qdma)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ int err;
+
+ err = airoha_qdma_init_rx_queue(&qdma->q_rx[i], qdma,
+ RX_DSCP_NUM);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int airoha_qdma_init_tx_queue(struct airoha_queue *q,
+ struct airoha_qdma *qdma, int size)
+{
+ int qid = q - &qdma->q_tx[0];
+ unsigned long dma_addr;
+
+ q->ndesc = size;
+ q->head = 0;
+
+ q->desc = dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+ if (!q->desc)
+ return -ENOMEM;
+
+ memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+ dma_map_single(q->desc, q->ndesc * sizeof(*q->desc), DMA_TO_DEVICE);
+
+ airoha_qdma_wr(qdma, REG_TX_RING_BASE(qid), dma_addr);
+ airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid), TX_RING_CPU_IDX_MASK,
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, q->head));
+ airoha_qdma_rmw(qdma, REG_TX_DMA_IDX(qid), TX_RING_DMA_IDX_MASK,
+ FIELD_PREP(TX_RING_DMA_IDX_MASK, q->head));
+
+ return 0;
+}
+
+static int airoha_qdma_tx_irq_init(struct airoha_tx_irq_queue *irq_q,
+ struct airoha_qdma *qdma, int size)
+{
+ int id = irq_q - &qdma->q_tx_irq[0];
+ unsigned long dma_addr;
+
+ irq_q->q = dma_alloc_coherent(size * sizeof(u32), &dma_addr);
+ if (!irq_q->q)
+ return -ENOMEM;
+
+ memset(irq_q->q, 0xffffffff, size * sizeof(u32));
+ irq_q->size = size;
+ irq_q->qdma = qdma;
+
+ dma_map_single(irq_q->q, size * sizeof(u32), DMA_TO_DEVICE);
+
+ airoha_qdma_wr(qdma, REG_TX_IRQ_BASE(id), dma_addr);
+ airoha_qdma_rmw(qdma, REG_TX_IRQ_CFG(id), TX_IRQ_DEPTH_MASK,
+ FIELD_PREP(TX_IRQ_DEPTH_MASK, size));
+
+ return 0;
+}
+
+static int airoha_qdma_init_tx(struct airoha_qdma *qdma)
+{
+ int i, err;
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx_irq); i++) {
+ err = airoha_qdma_tx_irq_init(&qdma->q_tx_irq[i], qdma,
+ IRQ_QUEUE_LEN);
+ if (err)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_tx); i++) {
+ err = airoha_qdma_init_tx_queue(&qdma->q_tx[i], qdma,
+ TX_DSCP_NUM);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
+{
+ unsigned long dma_addr;
+ u32 status;
+ int size;
+
+ size = HW_DSCP_NUM * sizeof(struct airoha_qdma_fwd_desc);
+ qdma->hfwd.desc = dma_alloc_coherent(size, &dma_addr);
+ if (!qdma->hfwd.desc)
+ return -ENOMEM;
+
+ memset(qdma->hfwd.desc, 0, size);
+ dma_map_single(qdma->hfwd.desc, size, DMA_TO_DEVICE);
+
+ airoha_qdma_wr(qdma, REG_FWD_DSCP_BASE, dma_addr);
+
+ size = AIROHA_MAX_PACKET_SIZE * HW_DSCP_NUM;
+ qdma->hfwd.q = dma_alloc_coherent(size, &dma_addr);
+ if (!qdma->hfwd.q)
+ return -ENOMEM;
+
+ memset(qdma->hfwd.q, 0, size);
+ dma_map_single(qdma->hfwd.q, size, DMA_TO_DEVICE);
+
+ airoha_qdma_wr(qdma, REG_FWD_BUF_BASE, dma_addr);
+
+ airoha_qdma_rmw(qdma, REG_HW_FWD_DSCP_CFG,
+ HW_FWD_DSCP_PAYLOAD_SIZE_MASK |
+ HW_FWD_DSCP_MIN_SCATTER_LEN_MASK,
+ FIELD_PREP(HW_FWD_DSCP_PAYLOAD_SIZE_MASK, 0) |
+ FIELD_PREP(HW_FWD_DSCP_MIN_SCATTER_LEN_MASK, 1));
+ airoha_qdma_rmw(qdma, REG_LMGR_INIT_CFG,
+ LMGR_INIT_START | LMGR_SRAM_MODE_MASK |
+ HW_FWD_DESC_NUM_MASK,
+ FIELD_PREP(HW_FWD_DESC_NUM_MASK, HW_DSCP_NUM) |
+ LMGR_INIT_START);
+
+ udelay(1000);
+ return read_poll_timeout(airoha_qdma_rr, status,
+ !(status & LMGR_INIT_START), USEC_PER_MSEC,
+ 30 * USEC_PER_MSEC, qdma,
+ REG_LMGR_INIT_CFG);
+}
+
+static int airoha_qdma_hw_init(struct airoha_qdma *qdma)
+{
+ int i;
+
+ /* clear pending irqs */
+ for (i = 0; i < 2; i++)
+ airoha_qdma_wr(qdma, REG_INT_STATUS(i), 0xffffffff);
+
+ airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_CPU_TXR_RR_MASK |
+ GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK |
+ GLOBAL_CFG_IRQ0_EN_MASK |
+ GLOBAL_CFG_TX_WB_DONE_MASK |
+ FIELD_PREP(GLOBAL_CFG_MAX_ISSUE_NUM_MASK, 3));
+
+ /* disable qdma rx delay interrupt */
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ if (!qdma->q_rx[i].ndesc)
+ continue;
+
+ airoha_qdma_clear(qdma, REG_RX_DELAY_INT_IDX(i),
+ RX_DELAY_INT_MASK);
+ }
+
+ return 0;
+}
+
+static int airoha_qdma_init(struct udevice *dev,
+ struct airoha_eth *eth,
+ struct airoha_qdma *qdma)
+{
+ int err;
+
+ qdma->eth = eth;
+ qdma->regs = dev_remap_addr_name(dev, "qdma0");
+ if (IS_ERR(qdma->regs))
+ return PTR_ERR(qdma->regs);
+
+ err = airoha_qdma_init_rx(qdma);
+ if (err)
+ return err;
+
+ err = airoha_qdma_init_tx(qdma);
+ if (err)
+ return err;
+
+ err = airoha_qdma_init_hfwd_queues(qdma);
+ if (err)
+ return err;
+
+ return airoha_qdma_hw_init(qdma);
+}
+
+static int airoha_hw_init(struct udevice *dev,
+ struct airoha_eth *eth)
+{
+ int ret, i;
+
+ /* disable xsi */
+ ret = reset_assert_bulk(&eth->xsi_rsts);
+ if (ret)
+ return ret;
+
+ ret = reset_assert_bulk(&eth->rsts);
+ if (ret)
+ return ret;
+
+ mdelay(20);
+
+ ret = reset_deassert_bulk(&eth->rsts);
+ if (ret)
+ return ret;
+
+ mdelay(20);
+
+ ret = airoha_fe_init(eth);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(eth->qdma); i++) {
+ ret = airoha_qdma_init(dev, eth, &eth->qdma[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int airoha_switch_init(struct udevice *dev, struct airoha_eth *eth)
+{
+ ofnode switch_node;
+ fdt_addr_t addr;
+
+ switch_node = ofnode_by_compatible(ofnode_null(), "airoha,en7581-switch");
+ if (!ofnode_valid(switch_node))
+ return -EINVAL;
+
+ addr = ofnode_get_addr(switch_node);
+ if (addr == FDT_ADDR_T_NONE)
+ return -ENOMEM;
+
+ /* Switch doesn't have a DEV, gets address and setup Flood and CPU port */
+ eth->switch_regs = map_sysmem(addr, 0);
+
+ /* Set FLOOD, no CPU switch register */
+ airoha_switch_wr(eth, SWITCH_MFC, SWITCH_BC_FFP | SWITCH_UNM_FFP |
+ SWITCH_UNU_FFP);
+
+ /* Set CPU 6 PMCR */
+ airoha_switch_wr(eth, SWITCH_PMCR(6),
+ SWITCH_IPG_CFG_SHORT | SWITCH_MAC_MODE |
+ SWITCH_FORCE_MODE | SWITCH_MAC_TX_EN |
+ SWITCH_MAC_RX_EN | SWITCH_BKOFF_EN | SWITCH_BKPR_EN |
+ SWITCH_FORCE_RX_FC | SWITCH_FORCE_TX_FC |
+ SWITCH_FORCE_SPD_1000 | SWITCH_FORCE_DPX |
+ SWITCH_FORCE_LNK);
+
+ /* Sideband signal error for Port 3, which need the auto polling */
+ airoha_switch_wr(eth, SWITCH_PHY_POLL,
+ FIELD_PREP(SWITCH_PHY_AP_EN, 0x7f) |
+ FIELD_PREP(SWITCH_EEE_POLL_EN, 0x7f) |
+ SWITCH_PHY_PRE_EN |
+ FIELD_PREP(SWITCH_PHY_END_ADDR, 0xc) |
+ FIELD_PREP(SWITCH_PHY_ST_ADDR, 0x8));
+
+ return 0;
+}
+
+static int airoha_eth_probe(struct udevice *dev)
+{
+ struct airoha_eth *eth = dev_get_priv(dev);
+ struct regmap *scu_regmap;
+ ofnode scu_node;
+ int ret;
+
+ scu_node = ofnode_by_compatible(ofnode_null(), "airoha,en7581-scu");
+ if (!ofnode_valid(scu_node))
+ return -EINVAL;
+
+ scu_regmap = syscon_node_to_regmap(scu_node);
+ if (IS_ERR(scu_regmap))
+ return PTR_ERR(scu_regmap);
+
+ /* It seems by default the FEMEM_SEL is set to Memory (0x1)
+ * preventing any access to any QDMA and FrameEngine register
+ * reporting all 0xdeadbeef (poor cow :( )
+ */
+ regmap_write(scu_regmap, SCU_SHARE_FEMEM_SEL, 0x0);
+
+ eth->fe_regs = dev_remap_addr_name(dev, "fe");
+ if (!eth->fe_regs)
+ return -ENOMEM;
+
+ eth->rsts.resets = devm_kcalloc(dev, AIROHA_MAX_NUM_RSTS,
+ sizeof(struct reset_ctl), GFP_KERNEL);
+ if (!eth->rsts.resets)
+ return -ENOMEM;
+ eth->rsts.count = AIROHA_MAX_NUM_RSTS;
+
+ eth->xsi_rsts.resets = devm_kcalloc(dev, AIROHA_MAX_NUM_XSI_RSTS,
+ sizeof(struct reset_ctl), GFP_KERNEL);
+ if (!eth->xsi_rsts.resets)
+ return -ENOMEM;
+ eth->xsi_rsts.count = AIROHA_MAX_NUM_XSI_RSTS;
+
+ ret = reset_get_by_name(dev, "fe", &eth->rsts.resets[0]);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "pdma", &eth->rsts.resets[1]);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "qdma", &eth->rsts.resets[2]);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "hsi0-mac", &eth->xsi_rsts.resets[0]);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "hsi1-mac", &eth->xsi_rsts.resets[1]);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "hsi-mac", &eth->xsi_rsts.resets[2]);
+ if (ret)
+ return ret;
+
+ ret = reset_get_by_name(dev, "xfp-mac", &eth->xsi_rsts.resets[3]);
+ if (ret)
+ return ret;
+
+ ret = airoha_hw_init(dev, eth);
+ if (ret)
+ return ret;
+
+ return airoha_switch_init(dev, eth);
+}
+
+static int airoha_eth_init(struct udevice *dev)
+{
+ struct airoha_eth *eth = dev_get_priv(dev);
+ struct airoha_qdma *qdma = &eth->qdma[0];
+ struct airoha_queue *q;
+ int qid;
+
+ qid = 0;
+ q = &qdma->q_rx[qid];
+
+ airoha_qdma_init_rx_desc(q);
+
+ airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+
+ return 0;
+}
+
+static void airoha_eth_stop(struct udevice *dev)
+{
+ struct airoha_eth *eth = dev_get_priv(dev);
+ struct airoha_qdma *qdma = &eth->qdma[0];
+
+ airoha_qdma_clear(qdma, REG_QDMA_GLOBAL_CFG,
+ GLOBAL_CFG_TX_DMA_EN_MASK |
+ GLOBAL_CFG_RX_DMA_EN_MASK);
+}
+
+static int airoha_eth_send(struct udevice *dev, void *packet, int length)
+{
+ struct airoha_eth *eth = dev_get_priv(dev);
+ struct airoha_qdma *qdma = &eth->qdma[0];
+ struct airoha_qdma_desc *desc;
+ struct airoha_queue *q;
+ dma_addr_t dma_addr;
+ u32 msg0, msg1;
+ int qid, index;
+ u8 fport;
+ u32 val;
+ int i;
+
+ dma_addr = dma_map_single(packet, length, DMA_TO_DEVICE);
+
+ qid = 0;
+ q = &qdma->q_tx[qid];
+ desc = &q->desc[q->head];
+ index = (q->head + 1) % q->ndesc;
+
+ fport = 1;
+
+ msg0 = 0;
+ msg1 = FIELD_PREP(QDMA_ETH_TXMSG_FPORT_MASK, fport) |
+ FIELD_PREP(QDMA_ETH_TXMSG_METER_MASK, 0x7f);
+
+ val = FIELD_PREP(QDMA_DESC_LEN_MASK, length);
+ WRITE_ONCE(desc->ctrl, cpu_to_le32(val));
+ WRITE_ONCE(desc->addr, cpu_to_le32(dma_addr));
+ val = FIELD_PREP(QDMA_DESC_NEXT_ID_MASK, index);
+ WRITE_ONCE(desc->data, cpu_to_le32(val));
+ WRITE_ONCE(desc->msg0, cpu_to_le32(msg0));
+ WRITE_ONCE(desc->msg1, cpu_to_le32(msg1));
+ WRITE_ONCE(desc->msg2, cpu_to_le32(0xffff));
+
+ dma_map_single(desc, sizeof(*desc), DMA_TO_DEVICE);
+
+ airoha_qdma_rmw(qdma, REG_TX_CPU_IDX(qid), TX_RING_CPU_IDX_MASK,
+ FIELD_PREP(TX_RING_CPU_IDX_MASK, index));
+
+ for (i = 0; i < 100; i++) {
+ dma_unmap_single(virt_to_phys(desc), sizeof(*desc),
+ DMA_FROM_DEVICE);
+ if (desc->ctrl & QDMA_DESC_DONE_MASK)
+ break;
+
+ udelay(1);
+ }
+
+ /* Return error if for some reason the descriptor never ACK */
+ if (!(desc->ctrl & QDMA_DESC_DONE_MASK))
+ return -EAGAIN;
+
+ q->head = index;
+ airoha_qdma_rmw(qdma, REG_IRQ_CLEAR_LEN(0),
+ IRQ_CLEAR_LEN_MASK, 1);
+
+ return 0;
+}
+
+static int airoha_eth_recv(struct udevice *dev, int flags, uchar **packetp)
+{
+ struct airoha_eth *eth = dev_get_priv(dev);
+ struct airoha_qdma *qdma = &eth->qdma[0];
+ struct airoha_qdma_desc *desc;
+ struct airoha_queue *q;
+ u16 length;
+ int qid;
+
+ qid = 0;
+ q = &qdma->q_rx[qid];
+ desc = &q->desc[q->head];
+
+ dma_unmap_single(virt_to_phys(desc), sizeof(*desc),
+ DMA_FROM_DEVICE);
+
+ if (!(desc->ctrl & QDMA_DESC_DONE_MASK))
+ return -EAGAIN;
+
+ length = FIELD_GET(QDMA_DESC_LEN_MASK, desc->ctrl);
+ dma_unmap_single(desc->addr, length,
+ DMA_FROM_DEVICE);
+
+ *packetp = phys_to_virt(desc->addr);
+
+ return length;
+}
+
+static int arht_eth_free_pkt(struct udevice *dev, uchar *packet, int length)
+{
+ struct airoha_eth *eth = dev_get_priv(dev);
+ struct airoha_qdma *qdma = &eth->qdma[0];
+ struct airoha_queue *q;
+ int qid;
+
+ if (!packet)
+ return 0;
+
+ qid = 0;
+ q = &qdma->q_rx[qid];
+
+ dma_map_single(packet, length, DMA_TO_DEVICE);
+
+ airoha_qdma_reset_rx_desc(q, q->head, packet);
+
+ airoha_qdma_rmw(qdma, REG_RX_CPU_IDX(qid), RX_RING_CPU_IDX_MASK,
+ FIELD_PREP(RX_RING_CPU_IDX_MASK, q->head));
+ q->head = (q->head + 1) % q->ndesc;
+
+ return 0;
+}
+
+static int arht_eth_write_hwaddr(struct udevice *dev)
+{
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct airoha_eth *eth = dev_get_priv(dev);
+ unsigned char *mac = pdata->enetaddr;
+ u32 macaddr_lsb, macaddr_msb;
+
+ macaddr_lsb = FIELD_PREP(SMACCR0_MAC2, mac[2]) |
+ FIELD_PREP(SMACCR0_MAC3, mac[3]) |
+ FIELD_PREP(SMACCR0_MAC4, mac[4]) |
+ FIELD_PREP(SMACCR0_MAC5, mac[5]);
+ macaddr_msb = FIELD_PREP(SMACCR1_MAC1, mac[1]) |
+ FIELD_PREP(SMACCR1_MAC0, mac[0]);
+
+ /* Set MAC for Switch */
+ airoha_switch_wr(eth, SWITCH_SMACCR0, macaddr_lsb);
+ airoha_switch_wr(eth, SWITCH_SMACCR1, macaddr_msb);
+
+ return 0;
+}
+
+static const struct udevice_id airoha_eth_ids[] = {
+ { .compatible = "airoha,en7581-eth" },
+};
+
+static const struct eth_ops airoha_eth_ops = {
+ .start = airoha_eth_init,
+ .stop = airoha_eth_stop,
+ .send = airoha_eth_send,
+ .recv = airoha_eth_recv,
+ .free_pkt = arht_eth_free_pkt,
+ .write_hwaddr = arht_eth_write_hwaddr,
+};
+
+U_BOOT_DRIVER(airoha_eth) = {
+ .name = "airoha-eth",
+ .id = UCLASS_ETH,
+ .of_match = airoha_eth_ids,
+ .probe = airoha_eth_probe,
+ .ops = &airoha_eth_ops,
+ .priv_auto = sizeof(struct airoha_eth),
+ .plat_auto = sizeof(struct eth_pdata),
+};
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index f475f341c9c..a3513f0a3ef 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -59,6 +59,15 @@ config ADI_SPI3
Enable the ADI (Analog Devices) SPI controller driver. This
driver enables the support for SC5XX spi controller.
+config AIROHA_SNFI_SPI
+ bool "Airoha SPI memory controller driver"
+ depends on SPI_MEM
+ help
+ Enable the Airoha SPI memory controller driver. This driver is
+ originally based on the Airoha SNFI IP core. It can only be
+ used to access SPI memory devices like SPI-NOR or SPI-NAND on
+ platforms embedding this IP core, like AN7581.
+
config ALTERA_SPI
bool "Altera SPI driver"
help
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 21895d46429..da91b18b6ed 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_SPI_MEM) += spi-mem-nodm.o
endif
obj-$(CONFIG_ADI_SPI3) += adi_spi3.o
+obj-$(CONFIG_AIROHA_SNFI_SPI) += airoha_snfi_spi.o
obj-$(CONFIG_ALTERA_SPI) += altera_spi.o
obj-$(CONFIG_APPLE_SPI) += apple_spi.o
obj-$(CONFIG_ATH79_SPI) += ath79_spi.o
diff --git a/drivers/spi/airoha_snfi_spi.c b/drivers/spi/airoha_snfi_spi.c
new file mode 100644
index 00000000000..3ea25b293d1
--- /dev/null
+++ b/drivers/spi/airoha_snfi_spi.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 AIROHA Inc
+ *
+ * Based on spi-airoha-snfi.c on Linux
+ *
+ * Author: Lorenzo Bianconi <lorenzo@kernel.org>
+ * Author: Ray Liu <ray.liu@airoha.com>
+ */
+
+#include <asm/unaligned.h>
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/spinand.h>
+#include <linux/time.h>
+#include <regmap.h>
+#include <spi.h>
+#include <spi-mem.h>
+
+/* SPI */
+#define REG_SPI_CTRL_READ_MODE 0x0000
+#define REG_SPI_CTRL_READ_IDLE_EN 0x0004
+#define REG_SPI_CTRL_SIDLY 0x0008
+#define REG_SPI_CTRL_CSHEXT 0x000c
+#define REG_SPI_CTRL_CSLEXT 0x0010
+
+#define REG_SPI_CTRL_MTX_MODE_TOG 0x0014
+#define SPI_CTRL_MTX_MODE_TOG GENMASK(3, 0)
+
+#define REG_SPI_CTRL_RDCTL_FSM 0x0018
+#define SPI_CTRL_RDCTL_FSM GENMASK(3, 0)
+
+#define REG_SPI_CTRL_MACMUX_SEL 0x001c
+
+#define REG_SPI_CTRL_MANUAL_EN 0x0020
+#define SPI_CTRL_MANUAL_EN BIT(0)
+
+#define REG_SPI_CTRL_OPFIFO_EMPTY 0x0024
+#define SPI_CTRL_OPFIFO_EMPTY BIT(0)
+
+#define REG_SPI_CTRL_OPFIFO_WDATA 0x0028
+#define SPI_CTRL_OPFIFO_LEN GENMASK(8, 0)
+#define SPI_CTRL_OPFIFO_OP GENMASK(13, 9)
+
+#define REG_SPI_CTRL_OPFIFO_FULL 0x002c
+#define SPI_CTRL_OPFIFO_FULL BIT(0)
+
+#define REG_SPI_CTRL_OPFIFO_WR 0x0030
+#define SPI_CTRL_OPFIFO_WR BIT(0)
+
+#define REG_SPI_CTRL_DFIFO_FULL 0x0034
+#define SPI_CTRL_DFIFO_FULL BIT(0)
+
+#define REG_SPI_CTRL_DFIFO_WDATA 0x0038
+#define SPI_CTRL_DFIFO_WDATA GENMASK(7, 0)
+
+#define REG_SPI_CTRL_DFIFO_EMPTY 0x003c
+#define SPI_CTRL_DFIFO_EMPTY BIT(0)
+
+#define REG_SPI_CTRL_DFIFO_RD 0x0040
+#define SPI_CTRL_DFIFO_RD BIT(0)
+
+#define REG_SPI_CTRL_DFIFO_RDATA 0x0044
+#define SPI_CTRL_DFIFO_RDATA GENMASK(7, 0)
+
+#define REG_SPI_CTRL_DUMMY 0x0080
+#define SPI_CTRL_CTRL_DUMMY GENMASK(3, 0)
+
+#define REG_SPI_CTRL_PROBE_SEL 0x0088
+#define REG_SPI_CTRL_INTERRUPT 0x0090
+#define REG_SPI_CTRL_INTERRUPT_EN 0x0094
+#define REG_SPI_CTRL_SI_CK_SEL 0x009c
+#define REG_SPI_CTRL_SW_CFGNANDADDR_VAL 0x010c
+#define REG_SPI_CTRL_SW_CFGNANDADDR_EN 0x0110
+#define REG_SPI_CTRL_SFC_STRAP 0x0114
+
+#define REG_SPI_CTRL_NFI2SPI_EN 0x0130
+#define SPI_CTRL_NFI2SPI_EN BIT(0)
+
+/* NFI2SPI */
+#define REG_SPI_NFI_CNFG 0x0000
+#define SPI_NFI_DMA_MODE BIT(0)
+#define SPI_NFI_READ_MODE BIT(1)
+#define SPI_NFI_DMA_BURST_EN BIT(2)
+#define SPI_NFI_HW_ECC_EN BIT(8)
+#define SPI_NFI_AUTO_FDM_EN BIT(9)
+#define SPI_NFI_OPMODE GENMASK(14, 12)
+
+#define REG_SPI_NFI_PAGEFMT 0x0004
+#define SPI_NFI_PAGE_SIZE GENMASK(1, 0)
+#define SPI_NFI_SPARE_SIZE GENMASK(5, 4)
+
+#define REG_SPI_NFI_CON 0x0008
+#define SPI_NFI_FIFO_FLUSH BIT(0)
+#define SPI_NFI_RST BIT(1)
+#define SPI_NFI_RD_TRIG BIT(8)
+#define SPI_NFI_WR_TRIG BIT(9)
+#define SPI_NFI_SEC_NUM GENMASK(15, 12)
+
+#define REG_SPI_NFI_INTR_EN 0x0010
+#define SPI_NFI_RD_DONE_EN BIT(0)
+#define SPI_NFI_WR_DONE_EN BIT(1)
+#define SPI_NFI_RST_DONE_EN BIT(2)
+#define SPI_NFI_ERASE_DONE_EN BIT(3)
+#define SPI_NFI_BUSY_RETURN_EN BIT(4)
+#define SPI_NFI_ACCESS_LOCK_EN BIT(5)
+#define SPI_NFI_AHB_DONE_EN BIT(6)
+#define SPI_NFI_ALL_IRQ_EN \
+ (SPI_NFI_RD_DONE_EN | SPI_NFI_WR_DONE_EN | \
+ SPI_NFI_RST_DONE_EN | SPI_NFI_ERASE_DONE_EN | \
+ SPI_NFI_BUSY_RETURN_EN | SPI_NFI_ACCESS_LOCK_EN | \
+ SPI_NFI_AHB_DONE_EN)
+
+#define REG_SPI_NFI_INTR 0x0014
+#define SPI_NFI_AHB_DONE BIT(6)
+
+#define REG_SPI_NFI_CMD 0x0020
+
+#define REG_SPI_NFI_ADDR_NOB 0x0030
+#define SPI_NFI_ROW_ADDR_NOB GENMASK(6, 4)
+
+#define REG_SPI_NFI_STA 0x0060
+#define REG_SPI_NFI_FIFOSTA 0x0064
+#define REG_SPI_NFI_STRADDR 0x0080
+#define REG_SPI_NFI_FDM0L 0x00a0
+#define REG_SPI_NFI_FDM0M 0x00a4
+#define REG_SPI_NFI_FDM7L 0x00d8
+#define REG_SPI_NFI_FDM7M 0x00dc
+#define REG_SPI_NFI_FIFODATA0 0x0190
+#define REG_SPI_NFI_FIFODATA1 0x0194
+#define REG_SPI_NFI_FIFODATA2 0x0198
+#define REG_SPI_NFI_FIFODATA3 0x019c
+#define REG_SPI_NFI_MASTERSTA 0x0224
+
+#define REG_SPI_NFI_SECCUS_SIZE 0x022c
+#define SPI_NFI_CUS_SEC_SIZE GENMASK(12, 0)
+#define SPI_NFI_CUS_SEC_SIZE_EN BIT(16)
+
+#define REG_SPI_NFI_RD_CTL2 0x0510
+#define REG_SPI_NFI_RD_CTL3 0x0514
+
+#define REG_SPI_NFI_PG_CTL1 0x0524
+#define SPI_NFI_PG_LOAD_CMD GENMASK(15, 8)
+
+#define REG_SPI_NFI_PG_CTL2 0x0528
+#define REG_SPI_NFI_NOR_PROG_ADDR 0x052c
+#define REG_SPI_NFI_NOR_RD_ADDR 0x0534
+
+#define REG_SPI_NFI_SNF_MISC_CTL 0x0538
+#define SPI_NFI_DATA_READ_WR_MODE GENMASK(18, 16)
+
+#define REG_SPI_NFI_SNF_MISC_CTL2 0x053c
+#define SPI_NFI_READ_DATA_BYTE_NUM GENMASK(12, 0)
+#define SPI_NFI_PROG_LOAD_BYTE_NUM GENMASK(28, 16)
+
+#define REG_SPI_NFI_SNF_STA_CTL1 0x0550
+#define SPI_NFI_READ_FROM_CACHE_DONE BIT(25)
+#define SPI_NFI_LOAD_TO_CACHE_DONE BIT(26)
+
+#define REG_SPI_NFI_SNF_STA_CTL2 0x0554
+
+#define REG_SPI_NFI_SNF_NFI_CNFG 0x055c
+#define SPI_NFI_SPI_MODE BIT(0)
+
+/* SPI NAND Protocol OP */
+#define SPI_NAND_OP_GET_FEATURE 0x0f
+#define SPI_NAND_OP_SET_FEATURE 0x1f
+#define SPI_NAND_OP_PAGE_READ 0x13
+#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_QUAD 0x6b
+#define SPI_NAND_OP_WRITE_ENABLE 0x06
+#define SPI_NAND_OP_WRITE_DISABLE 0x04
+#define SPI_NAND_OP_PROGRAM_LOAD_SINGLE 0x02
+#define SPI_NAND_OP_PROGRAM_LOAD_QUAD 0x32
+#define SPI_NAND_OP_PROGRAM_LOAD_RAMDOM_SINGLE 0x84
+#define SPI_NAND_OP_PROGRAM_LOAD_RAMDON_QUAD 0x34
+#define SPI_NAND_OP_PROGRAM_EXECUTE 0x10
+#define SPI_NAND_OP_READ_ID 0x9f
+#define SPI_NAND_OP_BLOCK_ERASE 0xd8
+#define SPI_NAND_OP_RESET 0xff
+#define SPI_NAND_OP_DIE_SELECT 0xc2
+
+#define SPI_NAND_CACHE_SIZE (SZ_4K + SZ_256)
+#define SPI_MAX_TRANSFER_SIZE 511
+
+enum airoha_snand_mode {
+ SPI_MODE_AUTO,
+ SPI_MODE_MANUAL,
+ SPI_MODE_DMA,
+};
+
+enum airoha_snand_cs {
+ SPI_CHIP_SEL_HIGH,
+ SPI_CHIP_SEL_LOW,
+};
+
+struct airoha_snand_priv {
+ 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_priv *priv,
+ u8 op_cmd, int op_len)
+{
+ int err;
+ u32 val;
+
+ err = regmap_write(priv->regmap_ctrl, REG_SPI_CTRL_OPFIFO_WDATA,
+ FIELD_PREP(SPI_CTRL_OPFIFO_LEN, op_len) |
+ FIELD_PREP(SPI_CTRL_OPFIFO_OP, op_cmd));
+ if (err)
+ return err;
+
+ err = regmap_read_poll_timeout(priv->regmap_ctrl,
+ REG_SPI_CTRL_OPFIFO_FULL,
+ val, !(val & SPI_CTRL_OPFIFO_FULL),
+ 0, 250 * USEC_PER_MSEC);
+ if (err)
+ return err;
+
+ err = regmap_write(priv->regmap_ctrl, REG_SPI_CTRL_OPFIFO_WR,
+ SPI_CTRL_OPFIFO_WR);
+ if (err)
+ return err;
+
+ return regmap_read_poll_timeout(priv->regmap_ctrl,
+ REG_SPI_CTRL_OPFIFO_EMPTY,
+ val, (val & SPI_CTRL_OPFIFO_EMPTY),
+ 0, 250 * USEC_PER_MSEC);
+}
+
+static int airoha_snand_set_cs(struct airoha_snand_priv *priv, u8 cs)
+{
+ return airoha_snand_set_fifo_op(priv, cs, sizeof(cs));
+}
+
+static int airoha_snand_write_data_to_fifo(struct airoha_snand_priv *priv,
+ const u8 *data, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ int err;
+ u32 val;
+
+ /* 1. Wait until dfifo is not full */
+ err = regmap_read_poll_timeout(priv->regmap_ctrl,
+ REG_SPI_CTRL_DFIFO_FULL, val,
+ !(val & SPI_CTRL_DFIFO_FULL),
+ 0, 250 * USEC_PER_MSEC);
+ if (err)
+ return err;
+
+ /* 2. Write data to register DFIFO_WDATA */
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_DFIFO_WDATA,
+ FIELD_PREP(SPI_CTRL_DFIFO_WDATA, data[i]));
+ if (err)
+ return err;
+
+ /* 3. Wait until dfifo is not full */
+ err = regmap_read_poll_timeout(priv->regmap_ctrl,
+ REG_SPI_CTRL_DFIFO_FULL, val,
+ !(val & SPI_CTRL_DFIFO_FULL),
+ 0, 250 * USEC_PER_MSEC);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int airoha_snand_read_data_from_fifo(struct airoha_snand_priv *priv,
+ u8 *ptr, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ int err;
+ u32 val;
+
+ /* 1. wait until dfifo is not empty */
+ err = regmap_read_poll_timeout(priv->regmap_ctrl,
+ REG_SPI_CTRL_DFIFO_EMPTY, val,
+ !(val & SPI_CTRL_DFIFO_EMPTY),
+ 0, 250 * USEC_PER_MSEC);
+ if (err)
+ return err;
+
+ /* 2. read from dfifo to register DFIFO_RDATA */
+ err = regmap_read(priv->regmap_ctrl,
+ REG_SPI_CTRL_DFIFO_RDATA, &val);
+ if (err)
+ return err;
+
+ ptr[i] = FIELD_GET(SPI_CTRL_DFIFO_RDATA, val);
+ /* 3. enable register DFIFO_RD to read next byte */
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_DFIFO_RD, SPI_CTRL_DFIFO_RD);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int airoha_snand_set_mode(struct airoha_snand_priv *priv,
+ enum airoha_snand_mode mode)
+{
+ int err;
+
+ switch (mode) {
+ case SPI_MODE_MANUAL: {
+ u32 val;
+
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_NFI2SPI_EN, 0);
+ if (err)
+ return err;
+
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_READ_IDLE_EN, 0);
+ if (err)
+ return err;
+
+ err = regmap_read_poll_timeout(priv->regmap_ctrl,
+ REG_SPI_CTRL_RDCTL_FSM, val,
+ !(val & SPI_CTRL_RDCTL_FSM),
+ 0, 250 * USEC_PER_MSEC);
+ if (err)
+ return err;
+
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_MTX_MODE_TOG, 9);
+ if (err)
+ return err;
+
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_MANUAL_EN, SPI_CTRL_MANUAL_EN);
+ if (err)
+ return err;
+ break;
+ }
+ case SPI_MODE_DMA:
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_NFI2SPI_EN,
+ SPI_CTRL_MANUAL_EN);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_MTX_MODE_TOG, 0x0);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(priv->regmap_ctrl,
+ REG_SPI_CTRL_MANUAL_EN, 0x0);
+ if (err < 0)
+ return err;
+ break;
+ case SPI_MODE_AUTO:
+ default:
+ break;
+ }
+
+ return regmap_write(priv->regmap_ctrl, REG_SPI_CTRL_DUMMY, 0);
+}
+
+static int airoha_snand_write_data(struct airoha_snand_priv *priv, u8 cmd,
+ const u8 *data, int len)
+{
+ int i, data_len;
+
+ for (i = 0; i < len; i += data_len) {
+ int err;
+
+ data_len = min(len - i, SPI_MAX_TRANSFER_SIZE);
+ err = airoha_snand_set_fifo_op(priv, cmd, data_len);
+ if (err)
+ return err;
+
+ err = airoha_snand_write_data_to_fifo(priv, &data[i],
+ data_len);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int airoha_snand_read_data(struct airoha_snand_priv *priv, u8 *data,
+ int len)
+{
+ int i, data_len;
+
+ for (i = 0; i < len; i += data_len) {
+ int err;
+
+ data_len = min(len - i, SPI_MAX_TRANSFER_SIZE);
+ err = airoha_snand_set_fifo_op(priv, 0xc, data_len);
+ if (err)
+ return err;
+
+ err = airoha_snand_read_data_from_fifo(priv, &data[i],
+ data_len);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int airoha_snand_nfi_init(struct airoha_snand_priv *priv)
+{
+ int err;
+
+ /* switch to SNFI mode */
+ err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_SNF_NFI_CNFG,
+ SPI_NFI_SPI_MODE);
+ if (err)
+ return err;
+
+ /* Enable DMA */
+ return regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_INTR_EN,
+ SPI_NFI_ALL_IRQ_EN, SPI_NFI_AHB_DONE_EN);
+}
+
+static int airoha_snand_nfi_config(struct airoha_snand_priv *priv)
+{
+ int err;
+ u32 val;
+
+ err = regmap_write(priv->regmap_nfi, REG_SPI_NFI_CON,
+ SPI_NFI_FIFO_FLUSH | SPI_NFI_RST);
+ if (err)
+ return err;
+
+ /* auto FDM */
+ err = regmap_clear_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
+ SPI_NFI_AUTO_FDM_EN);
+ if (err)
+ return err;
+
+ /* HW ECC */
+ err = regmap_clear_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
+ SPI_NFI_HW_ECC_EN);
+ if (err)
+ return err;
+
+ /* DMA Burst */
+ err = regmap_set_bits(priv->regmap_nfi, REG_SPI_NFI_CNFG,
+ SPI_NFI_DMA_BURST_EN);
+ if (err)
+ return err;
+
+ /* page format */
+ switch (priv->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(priv->regmap_nfi, REG_SPI_NFI_PAGEFMT,
+ SPI_NFI_SPARE_SIZE, val);
+ if (err)
+ return err;
+
+ switch (priv->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(priv->regmap_nfi, REG_SPI_NFI_PAGEFMT,
+ SPI_NFI_PAGE_SIZE, val);
+ if (err)
+ return err;
+
+ /* sec num */
+ val = FIELD_PREP(SPI_NFI_SEC_NUM, priv->nfi_cfg.sec_num);
+ err = regmap_update_bits(priv->regmap_nfi, REG_SPI_NFI_CON,
+ SPI_NFI_SEC_NUM, val);
+ if (err)
+ return err;
+
+ /* enable cust sec size */
+ err = regmap_set_bits(priv->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, priv->nfi_cfg.sec_size);
+ return regmap_update_bits(priv->regmap_nfi,
+ REG_SPI_NFI_SECCUS_SIZE,
+ SPI_NFI_CUS_SEC_SIZE, val);
+}
+
+static int airoha_snand_adjust_op_size(struct spi_slave *slave,
+ struct spi_mem_op *op)
+{
+ size_t max_len;
+
+ 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;
+}
+
+static bool airoha_snand_supports_op(struct spi_slave *slave,
+ const struct spi_mem_op *op)
+{
+ if (!spi_mem_default_supports_op(slave, op))
+ return false;
+
+ if (op->cmd.buswidth != 1)
+ return false;
+
+ return (!op->addr.nbytes || op->addr.buswidth == 1) &&
+ (!op->dummy.nbytes || op->dummy.buswidth == 1) &&
+ (!op->data.nbytes || op->data.buswidth == 1);
+}
+
+static int airoha_snand_exec_op(struct spi_slave *slave,
+ const struct spi_mem_op *op)
+{
+ u8 data[8], cmd, opcode = op->cmd.opcode;
+ struct udevice *bus = slave->dev->parent;
+ struct airoha_snand_priv *priv;
+ int i, err;
+
+ priv = dev_get_priv(bus);
+
+ /* switch to manual mode */
+ err = airoha_snand_set_mode(priv, SPI_MODE_MANUAL);
+ if (err < 0)
+ return err;
+
+ err = airoha_snand_set_cs(priv, SPI_CHIP_SEL_LOW);
+ if (err < 0)
+ return err;
+
+ /* opcode */
+ err = airoha_snand_write_data(priv, 0x8, &opcode, sizeof(opcode));
+ if (err)
+ return err;
+
+ /* addr part */
+ cmd = opcode == SPI_NAND_OP_GET_FEATURE ? 0x11 : 0x8;
+ put_unaligned_be64(op->addr.val, data);
+
+ for (i = ARRAY_SIZE(data) - op->addr.nbytes;
+ i < ARRAY_SIZE(data); i++) {
+ err = airoha_snand_write_data(priv, cmd, &data[i],
+ sizeof(data[0]));
+ if (err)
+ return err;
+ }
+
+ /* dummy */
+ data[0] = 0xff;
+ for (i = 0; i < op->dummy.nbytes; i++) {
+ err = airoha_snand_write_data(priv, 0x8, &data[0],
+ sizeof(data[0]));
+ if (err)
+ return err;
+ }
+
+ /* data */
+ if (op->data.dir == SPI_MEM_DATA_IN) {
+ err = airoha_snand_read_data(priv, op->data.buf.in,
+ op->data.nbytes);
+ if (err)
+ return err;
+ } else {
+ err = airoha_snand_write_data(priv, 0x8, op->data.buf.out,
+ op->data.nbytes);
+ if (err)
+ return err;
+ }
+
+ return airoha_snand_set_cs(priv, SPI_CHIP_SEL_HIGH);
+}
+
+static int airoha_snand_probe(struct udevice *dev)
+{
+ struct airoha_snand_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_ctrl, 0);
+ if (ret) {
+ dev_err(dev, "failed to init spi ctrl regmap\n");
+ return ret;
+ }
+
+ ret = regmap_init_mem_index(dev_ofnode(dev), &priv->regmap_nfi, 1);
+ if (ret) {
+ dev_err(dev, "failed to init spi nfi regmap\n");
+ return ret;
+ }
+
+ priv->spi_clk = devm_clk_get(dev, "spi");
+ if (IS_ERR(priv->spi_clk)) {
+ dev_err(dev, "unable to get spi clk\n");
+ return PTR_ERR(priv->regmap_ctrl);
+ }
+ clk_enable(priv->spi_clk);
+
+ return airoha_snand_nfi_init(priv);
+}
+
+static int airoha_snand_nfi_set_speed(struct udevice *bus, uint speed)
+{
+ struct airoha_snand_priv *priv = dev_get_priv(bus);
+ int ret;
+
+ ret = clk_set_rate(priv->spi_clk, speed);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int airoha_snand_nfi_set_mode(struct udevice *bus, uint mode)
+{
+ return 0;
+}
+
+static int airoha_snand_nfi_setup(struct spi_slave *slave,
+ const struct spinand_info *spinand_info)
+{
+ struct udevice *bus = slave->dev->parent;
+ struct airoha_snand_priv *priv;
+ u32 sec_size, sec_num;
+ int pagesize, oobsize;
+
+ priv = dev_get_priv(bus);
+
+ pagesize = spinand_info->memorg.pagesize;
+ oobsize = spinand_info->memorg.oobsize;
+
+ if (pagesize == 2 * 1024)
+ sec_num = 4;
+ else if (pagesize == 4 * 1024)
+ sec_num = 8;
+ else
+ sec_num = 1;
+
+ sec_size = (pagesize + oobsize) / sec_num;
+
+ /* init default value */
+ priv->nfi_cfg.sec_size = sec_size;
+ priv->nfi_cfg.sec_num = sec_num;
+ priv->nfi_cfg.page_size = round_down(sec_size * sec_num, 1024);
+ priv->nfi_cfg.spare_size = 16;
+
+ return airoha_snand_nfi_config(priv);
+}
+
+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,
+};
+
+static const struct dm_spi_ops airoha_snfi_spi_ops = {
+ .mem_ops = &airoha_snand_mem_ops,
+ .set_speed = airoha_snand_nfi_set_speed,
+ .set_mode = airoha_snand_nfi_set_mode,
+ .setup_for_spinand = airoha_snand_nfi_setup,
+};
+
+static const struct udevice_id airoha_snand_ids[] = {
+ { .compatible = "airoha,en7581-snand" },
+ { }
+};
+
+U_BOOT_DRIVER(airoha_snfi_spi) = {
+ .name = "airoha-snfi-spi",
+ .id = UCLASS_SPI,
+ .of_match = airoha_snand_ids,
+ .ops = &airoha_snfi_spi_ops,
+ .priv_auto = sizeof(struct airoha_snand_priv),
+ .probe = airoha_snand_probe,
+};
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 13b5a52f8b9..6fe6fd520a4 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -17,7 +17,7 @@
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#else
-#include <spi.h>
+#include <linux/bitops.h>
#include <spi-mem.h>
#include <linux/mtd/nand.h>
#endif
diff --git a/include/regmap.h b/include/regmap.h
index 22b043408ac..8c6f7c1c9b1 100644
--- a/include/regmap.h
+++ b/include/regmap.h
@@ -362,6 +362,34 @@ int regmap_raw_read_range(struct regmap *map, uint range_num, uint offset,
int regmap_update_bits(struct regmap *map, uint offset, uint mask, uint val);
/**
+ * regmap_set_bits() - Set bits to a regmap
+ *
+ * @map: Regmap to write bits to
+ * @offset: Offset in the regmap to write to
+ * @bits: Bits to set to the regmap at the specified offset
+ *
+ * Return: 0 if OK, -ve on error
+ */
+static inline int regmap_set_bits(struct regmap *map, uint offset, uint bits)
+{
+ return regmap_update_bits(map, offset, bits, bits);
+}
+
+/**
+ * regmap_clear_bits() - Clear bits to a regmap
+ *
+ * @map: Regmap to write bits to
+ * @offset: Offset in the regmap to write to
+ * @bits: Bits to clear to the regmap at the specified offset
+ *
+ * Return: 0 if OK, -ve on error
+ */
+static inline int regmap_clear_bits(struct regmap *map, uint offset, uint bits)
+{
+ return regmap_update_bits(map, offset, bits, 0);
+}
+
+/**
* regmap_init_mem() - Set up a new register map that uses memory access
*
* @node: Device node that uses this map
diff --git a/include/spi.h b/include/spi.h
index 6944773b596..2783200d663 100644
--- a/include/spi.h
+++ b/include/spi.h
@@ -11,6 +11,8 @@
#include <linux/bitops.h>
+struct spinand_info;
+
/* SPI mode flags */
#define SPI_CPHA BIT(0) /* clock phase (1 = SPI_CLOCK_PHASE_SECOND) */
#define SPI_CPOL BIT(1) /* clock polarity (1 = SPI_POLARITY_HIGH) */
@@ -537,6 +539,16 @@ struct dm_spi_ops {
*/
int (*get_mmap)(struct udevice *dev, ulong *map_basep,
uint *map_sizep, uint *offsetp);
+
+ /**
+ * setup_for_spinand() - Setup the SPI for attached SPI NAND
+ *
+ * @dev: The SPI flash slave device
+ * @spinand_info: The SPI NAND info to configure for
+ * @return 0 if OK, -ve value on error
+ */
+ int (*setup_for_spinand)(struct spi_slave *slave,
+ const struct spinand_info *spinand_info);
};
struct dm_spi_emul_ops {