diff options
-rw-r--r-- | arch/arm/dts/an7581-u-boot.dtsi | 90 | ||||
-rw-r--r-- | configs/an7581_evb_defconfig | 3 | ||||
-rw-r--r-- | drivers/mmc/Kconfig | 2 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/core.c | 18 | ||||
-rw-r--r-- | drivers/net/Kconfig | 8 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/airoha_eth.c | 948 | ||||
-rw-r--r-- | drivers/spi/Kconfig | 9 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/airoha_snfi_spi.c | 718 | ||||
-rw-r--r-- | include/linux/mtd/spinand.h | 2 | ||||
-rw-r--r-- | include/regmap.h | 28 | ||||
-rw-r--r-- | include/spi.h | 12 |
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(ð->xsi_rsts); + if (ret) + return ret; + + ret = reset_assert_bulk(ð->rsts); + if (ret) + return ret; + + mdelay(20); + + ret = reset_deassert_bulk(ð->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, ð->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", ð->rsts.resets[0]); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "pdma", ð->rsts.resets[1]); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "qdma", ð->rsts.resets[2]); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "hsi0-mac", ð->xsi_rsts.resets[0]); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "hsi1-mac", ð->xsi_rsts.resets[1]); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "hsi-mac", ð->xsi_rsts.resets[2]); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "xfp-mac", ð->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 = ð->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 = ð->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 = ð->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 = ð->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 = ð->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 { |