2024-12-19 21:34:44 +00:00
|
|
|
From 4504043cad841ed69c4d25b8d2a77452cc8c113f Mon Sep 17 00:00:00 2001
|
2024-12-15 18:29:23 +00:00
|
|
|
From: =?UTF-8?q?=E2=80=9Chuangyifeng=E2=80=9D?=
|
|
|
|
<huangyifeng@eswincomputing.com>
|
|
|
|
Date: Tue, 21 May 2024 10:39:21 +0800
|
|
|
|
Subject: [PATCH 011/219] feat(bootspi):Add bootspi flash driver
|
|
|
|
|
|
|
|
Changelogs:
|
|
|
|
1.Added bootspi flash driver
|
|
|
|
---
|
|
|
|
arch/riscv/configs/win2030_defconfig | 1 +
|
|
|
|
drivers/spi/Kconfig | 6 +
|
|
|
|
drivers/spi/Makefile | 1 +
|
|
|
|
drivers/spi/spi-eswin-bootspi.c | 658 +++++++++++++++++++++++++++
|
|
|
|
4 files changed, 666 insertions(+)
|
|
|
|
create mode 100644 drivers/spi/spi-eswin-bootspi.c
|
|
|
|
|
|
|
|
diff --git a/arch/riscv/configs/win2030_defconfig b/arch/riscv/configs/win2030_defconfig
|
|
|
|
index 725ad2400def..6733030403b9 100644
|
|
|
|
--- a/arch/riscv/configs/win2030_defconfig
|
|
|
|
+++ b/arch/riscv/configs/win2030_defconfig
|
|
|
|
@@ -139,6 +139,7 @@ CONFIG_SPI=y
|
|
|
|
CONFIG_SPI_DESIGNWARE=y
|
|
|
|
CONFIG_SPI_DW_DMA=y
|
|
|
|
CONFIG_SPI_DW_MMIO=y
|
|
|
|
+CONFIG_SPI_ESWIN_BOOTSPI=y
|
|
|
|
# CONFIG_PTP_1588_CLOCK is not set
|
|
|
|
CONFIG_PINCTRL=y
|
|
|
|
CONFIG_PINCTRL_EIC7700=y
|
|
|
|
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
|
|
|
|
index 3ce0fd5df8e9..07e874de0ebf 100644
|
|
|
|
--- a/drivers/spi/Kconfig
|
|
|
|
+++ b/drivers/spi/Kconfig
|
|
|
|
@@ -360,6 +360,12 @@ config SPI_EP93XX
|
|
|
|
This enables using the Cirrus EP93xx SPI controller in master
|
|
|
|
mode.
|
|
|
|
|
|
|
|
+config SPI_ESWIN_BOOTSPI
|
|
|
|
+ tristate "Eswin Computing Boot SPI controller"
|
|
|
|
+ help
|
|
|
|
+ This enables using the Eswin Computing Boot SPI controller in master
|
|
|
|
+ mode.
|
|
|
|
+
|
|
|
|
config SPI_FALCON
|
|
|
|
bool "Falcon SPI controller support"
|
|
|
|
depends on SOC_FALCON
|
|
|
|
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
|
|
|
|
index 6af54842b9fa..dc4c98c2dd90 100644
|
|
|
|
--- a/drivers/spi/Makefile
|
|
|
|
+++ b/drivers/spi/Makefile
|
|
|
|
@@ -50,6 +50,7 @@ obj-$(CONFIG_SPI_DW_BT1) += spi-dw-bt1.o
|
|
|
|
obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o
|
|
|
|
obj-$(CONFIG_SPI_DW_PCI) += spi-dw-pci.o
|
|
|
|
obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o
|
|
|
|
+obj-$(CONFIG_SPI_ESWIN_BOOTSPI) += spi-eswin-bootspi.o
|
|
|
|
obj-$(CONFIG_SPI_FALCON) += spi-falcon.o
|
|
|
|
obj-$(CONFIG_SPI_FSI) += spi-fsi.o
|
|
|
|
obj-$(CONFIG_SPI_FSL_CPM) += spi-fsl-cpm.o
|
|
|
|
diff --git a/drivers/spi/spi-eswin-bootspi.c b/drivers/spi/spi-eswin-bootspi.c
|
|
|
|
new file mode 100644
|
|
|
|
index 000000000000..c8fbc1b3fc91
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/spi/spi-eswin-bootspi.c
|
|
|
|
@@ -0,0 +1,658 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
|
|
+/*
|
|
|
|
+ * ESWIN BootSpi Driver
|
|
|
|
+ *
|
|
|
|
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
|
|
|
|
+ * SPDX-License-Identifier: GPL-2.0
|
|
|
|
+ *
|
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
|
|
+ * the Free Software Foundation, version 2.
|
|
|
|
+ *
|
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
+ * GNU General Public License for more details.
|
|
|
|
+ *
|
|
|
|
+ * You should have received a copy of the GNU General Public License
|
|
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
+ *
|
|
|
|
+ * Authors: HuangYiFeng<huangyifeng@eswincomputing.com>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/clk.h>
|
|
|
|
+#include <linux/reset.h>
|
|
|
|
+#include <linux/bitfield.h>
|
|
|
|
+#include <linux/interrupt.h>
|
|
|
|
+#include <linux/iopoll.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/mod_devicetable.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+#include <linux/slab.h>
|
|
|
|
+#include <linux/spi/spi.h>
|
|
|
|
+#include <linux/spi/spi-mem.h>
|
|
|
|
+#include <linux/mtd/spi-nor.h>
|
|
|
|
+
|
|
|
|
+/* Register offsets */
|
|
|
|
+#define ES_SPI_CSR_00 0x00 /*WRITE_STATUS_REG_TIME*/
|
|
|
|
+#define ES_SPI_CSR_01 0x04 /*SPI_BUS_MODE*/
|
|
|
|
+#define ES_SPI_CSR_02 0x08 /*ERASE_COUNTER_TAP*/
|
|
|
|
+#define ES_SPI_CSR_03 0x0c /*DMA_EN_HCLK_STATUS*/
|
|
|
|
+#define ES_SPI_CSR_04 0x10 /*FAST_READ_CONTROL*/
|
|
|
|
+#define ES_SPI_CSR_05 0x14 /*SPI_FLASH_WR_NUM*/
|
|
|
|
+#define ES_SPI_CSR_06 0x18 /*SPI_FLASH_COMMAND*/
|
|
|
|
+#define ES_SPI_CSR_07 0x1c /*INTERRUPT_CONTROL*/
|
|
|
|
+#define ES_SPI_CSR_08 0x20 /*DMA_REQUEST_TAP*/
|
|
|
|
+#define ES_SPI_CSR_09 0x24 /*SPI_FLASH_WR_ADDRESS*/
|
|
|
|
+#define ES_SPI_CSR_10 0x28 /*PAGE_PROGRAM_TIME*/
|
|
|
|
+#define ES_SPI_CSR_11 0x2c /*SECTOR_ERASE_TIME*/
|
|
|
|
+#define ES_SPI_CSR_12 0x30 /*SMALL_BLOCK_ERASE_TIME*/
|
|
|
|
+#define ES_SPI_CSR_13 0x34 /*LARGE_BLOCK_ERASE_TIME*/
|
|
|
|
+#define ES_SPI_CSR_14 0x38 /*CHIP_ERASE_TIME*/
|
|
|
|
+#define ES_SPI_CSR_15 0x3c /*CHIP_DESELECT_TIME*/
|
|
|
|
+#define ES_SPI_CSR_16 0x40 /*POWER_DOWN_TIME*/
|
|
|
|
+
|
|
|
|
+#define ES_SYSCSR_SPIMODECFG 0x340
|
|
|
|
+
|
|
|
|
+#define ES_CONCSR_SPI_INTSEL 0x3c0
|
|
|
|
+
|
|
|
|
+#define SPI_COMMAND_VALID 0x01
|
|
|
|
+#define SPI_COMMAND_MOVE_VALUE 0x00
|
|
|
|
+#define SPI_COMMAND_CODE_FIELD_POSITION 0X06
|
|
|
|
+#define SPI_COMMAND_MOVE_FIELD_POSITION 0X05
|
|
|
|
+#define SPI_COMMAND_TYPE_FIELD_POSITION 0X01
|
|
|
|
+
|
|
|
|
+/* Bit fields in CTRLR0 */
|
|
|
|
+/*
|
|
|
|
+ * Only present when SSI_MAX_XFER_SIZE=16. This is the default, and the only
|
|
|
|
+ * option before version 3.23a.
|
|
|
|
+ */
|
|
|
|
+#define SPI_INTSEL_MASK GENMASK(11, 10)
|
|
|
|
+#define INT_ROUTED_U84 0x0
|
|
|
|
+#define INT_ROUTED_LPCPU 0x1
|
|
|
|
+#define INT_ROUTED_SCPU 0x3u
|
|
|
|
+
|
|
|
|
+#define RX_TIMEOUT 5000 /* timeout in ms */
|
|
|
|
+
|
|
|
|
+#define SPI_COMMAND_INIT_VALUE 0XFFFFC000
|
|
|
|
+#define FLASH_PAGE_SIZE 0x100
|
|
|
|
+
|
|
|
|
+typedef enum {
|
|
|
|
+ SPI_FLASH_WR_BYTE = 1,
|
|
|
|
+ SPI_FLASH_WR_2BYTE = 2,
|
|
|
|
+ SPI_FLASH_WR_WORD = 4,
|
|
|
|
+} SPI_FLASH_WR_NUM_T;
|
|
|
|
+
|
|
|
|
+typedef enum {
|
|
|
|
+ SPI_FAST_READ_DEFAULT = 0,
|
|
|
|
+ SPI_FAST_READ_ENABLE = 3 /*WHEN SPI QUAD0 OR DUAL MODE*/
|
|
|
|
+} SPI_FAST_READ_CTL_T;
|
|
|
|
+
|
|
|
|
+typedef enum { STANDARD_SPI = 0, DUAL_SPI, QUAD_SPI } SPI_BUS_MODE_T;
|
|
|
|
+
|
|
|
|
+typedef enum {
|
|
|
|
+ SPIC_CMD_TYPE_SPI_PROGRAM = 0,
|
|
|
|
+ SPIC_CMD_TYPE_WRITE_STATUS_REGISTER,
|
|
|
|
+ SPIC_CMD_TYPE_READ_STATUS_REGISTER,
|
|
|
|
+ SPIC_CMD_TYPE_SECTOR_ERASE,
|
|
|
|
+ SPIC_CMD_TYPE_BLOCK_ERASE_TYPE1,
|
|
|
|
+ SPIC_CMD_TYPE_BLOCK_ERASE_TYPE2,
|
|
|
|
+ SPIC_CMD_TYPE_CHIP_ERASE,
|
|
|
|
+ SPIC_CMD_TYPE_POWER_DOWN,
|
|
|
|
+ SPIC_CMD_TYPE_RELEASE_POWER_DOWM,
|
|
|
|
+ SPIC_CMD_TYPE_ENTER_OR_EXIT_32BIT_MODE,
|
|
|
|
+ SPIC_CMD_TYPE_READ_SECURITY_REG,
|
|
|
|
+ SPIC_CMD_TYPE_ERASE_SECURITY_REG,
|
|
|
|
+ SPIC_CMD_TYPE_WRITE_SECURITY_REG,
|
|
|
|
+ SPIC_CMD_TYPE_READ_DATA,
|
|
|
|
+ SPIC_CMD_TYPE_READ_MANUFACTURED_ID,
|
|
|
|
+ SPIC_CMD_TYPE_READ_JEDEC_ID
|
|
|
|
+} SPI_FLASH_COMMAND_TYPE_T;
|
|
|
|
+
|
|
|
|
+#define SPIC_CMD_CODE_POWER_DOWN 0xb9
|
|
|
|
+#define SPIC_CMD_CODE_RELEASE_POWER_DOWN 0xab
|
|
|
|
+#define SPIC_CMD_CODE_ENABLE_RESET 0x66
|
|
|
|
+#define SPIC_CMD_CODE_RESET 0x99
|
|
|
|
+
|
|
|
|
+struct es_spi_priv {
|
|
|
|
+ struct clk *cfg_clk;
|
|
|
|
+ struct clk *clk;
|
|
|
|
+ struct reset_control *rstc;
|
|
|
|
+ struct gpio_desc *cs_gpio; /* External chip-select gpio */
|
|
|
|
+
|
|
|
|
+ void __iomem *regs;
|
|
|
|
+ void __iomem *sys_regs;
|
|
|
|
+ void __iomem *flash_base;
|
|
|
|
+ unsigned int freq; /* Default frequency */
|
|
|
|
+ unsigned int mode;
|
|
|
|
+
|
|
|
|
+ const void *tx;
|
|
|
|
+ u32 opcode;
|
|
|
|
+ u32 cmd_type;
|
|
|
|
+ u64 addr;
|
|
|
|
+ void *rx;
|
|
|
|
+ u32 fifo_len; /* depth of the FIFO buffer */
|
|
|
|
+ u32 max_xfer; /* Maximum transfer size (in bits) */
|
|
|
|
+
|
|
|
|
+ int bits_per_word;
|
|
|
|
+ int len;
|
|
|
|
+ u8 cs; /* chip select pin */
|
|
|
|
+ u8 tmode; /* TR/TO/RO/EEPROM */
|
|
|
|
+ u8 type; /* SPI/SSP/MicroWire */
|
|
|
|
+ struct spi_controller *master;
|
|
|
|
+ struct device *dev;
|
|
|
|
+ int irq;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static inline u32 eswin_bootspi_read(struct es_spi_priv *priv, u32 offset)
|
|
|
|
+{
|
|
|
|
+ return readl(priv->regs + offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline void eswin_bootspi_write(struct es_spi_priv *priv, u32 offset, u32 val)
|
|
|
|
+{
|
|
|
|
+ writel(val, priv->regs + offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline u32 eswin_bootspi_data_read(struct es_spi_priv *priv, u32 offset)
|
|
|
|
+{
|
|
|
|
+ return readl(priv->flash_base + offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline void eswin_bootspi_data_write(struct es_spi_priv *priv, u32 offset, u32 val)
|
|
|
|
+{
|
|
|
|
+ writel(val, priv->flash_base + offset);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int eswin_bootspi_wait_over(struct es_spi_priv *priv)
|
|
|
|
+{
|
|
|
|
+ u32 val;
|
|
|
|
+ struct device *dev = priv->dev;
|
|
|
|
+
|
|
|
|
+ if (readl_poll_timeout(priv->regs + ES_SPI_CSR_06, val,
|
|
|
|
+ (!(val & 0x1)), 10, RX_TIMEOUT * 1000)) {
|
|
|
|
+ dev_err(dev, "eswin_bootspi_wait_over : timeout!!\n");
|
|
|
|
+ return -ETIMEDOUT;
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @brief spi read and write cfg
|
|
|
|
+ */
|
|
|
|
+static void eswin_bootspi_read_write_cfg(struct es_spi_priv *priv, u32 byte, u32 addr)
|
|
|
|
+{
|
|
|
|
+ eswin_bootspi_write(priv, ES_SPI_CSR_09, addr);
|
|
|
|
+ eswin_bootspi_write(priv, ES_SPI_CSR_05, byte);
|
|
|
|
+ eswin_bootspi_write(priv, ES_SPI_CSR_04, SPI_FAST_READ_DEFAULT);
|
|
|
|
+ eswin_bootspi_write(priv, ES_SPI_CSR_01, STANDARD_SPI);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @brief write data from dest address to flash
|
|
|
|
+ */
|
|
|
|
+static void eswin_bootspi_send_data(struct es_spi_priv *priv,
|
|
|
|
+ const u8 *dest, u32 size)
|
|
|
|
+{
|
|
|
|
+ u32 offset = 0;
|
|
|
|
+ u8 *buff = (u8 *)dest;
|
|
|
|
+ u32 data = 0;
|
|
|
|
+ int i;
|
|
|
|
+ struct device *dev = priv->dev;
|
|
|
|
+
|
|
|
|
+ dev_dbg(dev,"wrtie spi data\n");
|
|
|
|
+ while (size >= SPI_FLASH_WR_WORD) {
|
|
|
|
+ data = (buff[0]) | (buff[1] << 8) |(buff[2] << 16) | (buff[3] << 24);
|
|
|
|
+ for (i = 0; i < 4; i++) {
|
|
|
|
+ dev_dbg(dev,"0x%x ", buff[i]);
|
|
|
|
+ }
|
|
|
|
+ dev_dbg(dev,"0x%x ", data);
|
|
|
|
+ eswin_bootspi_data_write(priv, offset, data);
|
|
|
|
+ offset = offset + SPI_FLASH_WR_WORD;
|
|
|
|
+ size = size - SPI_FLASH_WR_WORD;
|
|
|
|
+ buff = buff + SPI_FLASH_WR_WORD;
|
|
|
|
+ }
|
|
|
|
+ data = 0;
|
|
|
|
+ if (size != 0) {
|
|
|
|
+ for (i = 0; i < size; i++) {
|
|
|
|
+ data |=buff[i] << (8 * i);
|
|
|
|
+ dev_dbg(dev,"0x%x ", buff[i]);
|
|
|
|
+ }
|
|
|
|
+ dev_dbg(dev,"0x%x ", data);
|
|
|
|
+ eswin_bootspi_data_write(priv, offset, data);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @brief Read data from flash to dest address
|
|
|
|
+ */
|
|
|
|
+static void eswin_bootspi_recv_data(struct es_spi_priv *priv, u8 *dest, u32 size)
|
|
|
|
+{
|
|
|
|
+ u32 offset = 0;
|
|
|
|
+ u8 *buff = NULL;
|
|
|
|
+ u32 data = 0xFFFFFFFF;
|
|
|
|
+ int i;
|
|
|
|
+ struct device *dev = priv->dev;
|
|
|
|
+
|
|
|
|
+ dev_dbg(dev,"read spi data\n");
|
|
|
|
+ while (size >= SPI_FLASH_WR_WORD) {
|
|
|
|
+ buff = (u8 *)dest;
|
|
|
|
+ data = eswin_bootspi_data_read(priv, offset);
|
|
|
|
+ dev_dbg(dev,"0x%x ", data);
|
|
|
|
+ for (i = 0; i < SPI_FLASH_WR_WORD; i++) {
|
|
|
|
+ *buff = (u8)(data >> (8 * i));
|
|
|
|
+ dev_dbg(dev,"0x%x ", *buff);
|
|
|
|
+ buff++;
|
|
|
|
+ }
|
|
|
|
+ dest = dest + SPI_FLASH_WR_WORD;
|
|
|
|
+ offset = offset + SPI_FLASH_WR_WORD;
|
|
|
|
+ size = size - SPI_FLASH_WR_WORD;
|
|
|
|
+ }
|
|
|
|
+ if (size != 0) {
|
|
|
|
+ buff = (u8 *)dest;
|
|
|
|
+ data = eswin_bootspi_data_read(priv, offset);
|
|
|
|
+ for (i = 0; i < size; i++) {
|
|
|
|
+ *buff = (u8)(data >> (8 * i));
|
|
|
|
+ dev_dbg(dev,"0x%x ", *buff);
|
|
|
|
+ buff++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ dev_dbg(dev,"\n");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @brief spi send command
|
|
|
|
+ */
|
|
|
|
+static void eswin_bootspi_cmd_cfg(struct es_spi_priv *priv, u32 code, u32 type)
|
|
|
|
+{
|
|
|
|
+ u32 command = eswin_bootspi_read(priv, ES_SPI_CSR_06);
|
|
|
|
+ struct device *dev = priv->dev;
|
|
|
|
+
|
|
|
|
+ command &= ~((0xFF << 6) | (0x1 << 5) | (0xF << 1) | 0x1);
|
|
|
|
+ command |= ((code << SPI_COMMAND_CODE_FIELD_POSITION) |
|
|
|
|
+ (SPI_COMMAND_MOVE_VALUE << SPI_COMMAND_MOVE_FIELD_POSITION) |
|
|
|
|
+ (type << SPI_COMMAND_TYPE_FIELD_POSITION) | SPI_COMMAND_VALID);
|
|
|
|
+
|
|
|
|
+ eswin_bootspi_write(priv, ES_SPI_CSR_06, command);
|
|
|
|
+ dev_dbg(dev, "[%s %d]: write command 0x%x, read back command 0x%x\n",
|
|
|
|
+ __func__,__LINE__, command, eswin_bootspi_read(priv, ES_SPI_CSR_06));
|
|
|
|
+}
|
|
|
|
+/**
|
|
|
|
+ * @brief spi write flash
|
|
|
|
+ * @param [in] offset: address of flash to be write
|
|
|
|
+ * @param [in] wr_dest: Address of data to be sent
|
|
|
|
+ * @param [in] size: size of flash to be write
|
|
|
|
+ */
|
|
|
|
+void eswin_bootspi_writer(struct es_spi_priv *priv)
|
|
|
|
+{
|
|
|
|
+ u32 write_size = 0, offset, cmd_code;
|
|
|
|
+ u32 cmd_type = priv->cmd_type;
|
|
|
|
+ const u8 *wr_dest = priv->tx;
|
|
|
|
+ int size = priv->len;
|
|
|
|
+
|
|
|
|
+ offset = priv->addr;
|
|
|
|
+ cmd_code = priv->opcode;
|
|
|
|
+
|
|
|
|
+ if (size == 0) {
|
|
|
|
+ // if(SPIC_CMD_TYPE_SECTOR_ERASE == cmd_type)
|
|
|
|
+ {
|
|
|
|
+ eswin_bootspi_read_write_cfg(priv, write_size, offset);
|
|
|
|
+ eswin_bootspi_cmd_cfg(priv, cmd_code, cmd_type);
|
|
|
|
+ eswin_bootspi_wait_over(priv);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ while (size > 0) {
|
|
|
|
+ write_size = size;
|
|
|
|
+ if (write_size > FLASH_PAGE_SIZE) {
|
|
|
|
+ write_size = FLASH_PAGE_SIZE;
|
|
|
|
+ }
|
|
|
|
+ eswin_bootspi_read_write_cfg(priv, write_size, offset);
|
|
|
|
+ eswin_bootspi_send_data(priv, wr_dest, write_size);
|
|
|
|
+ eswin_bootspi_cmd_cfg(priv, cmd_code, cmd_type);
|
|
|
|
+ eswin_bootspi_wait_over(priv);
|
|
|
|
+ wr_dest += write_size;
|
|
|
|
+ offset += write_size;
|
|
|
|
+ size = size - write_size;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void eswin_bootspi_reader(struct es_spi_priv *priv)
|
|
|
|
+{
|
|
|
|
+ int read_size = 0;
|
|
|
|
+ u32 offset = priv->addr;
|
|
|
|
+ u32 cmd_code = priv->opcode;
|
|
|
|
+ u32 cmd_type = priv->cmd_type;
|
|
|
|
+ u8 *mem_dest = priv->rx;
|
|
|
|
+ int size = priv->len;
|
|
|
|
+
|
|
|
|
+ while (size > 0) {
|
|
|
|
+ read_size = size;
|
|
|
|
+ if (read_size > FLASH_PAGE_SIZE) {
|
|
|
|
+ read_size = FLASH_PAGE_SIZE;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ eswin_bootspi_read_write_cfg(priv, read_size, offset);
|
|
|
|
+ eswin_bootspi_cmd_cfg(priv, cmd_code, cmd_type);
|
|
|
|
+ eswin_bootspi_wait_over(priv);
|
|
|
|
+ eswin_bootspi_recv_data(priv, mem_dest, read_size);
|
|
|
|
+ mem_dest += read_size;
|
|
|
|
+ offset += read_size;
|
|
|
|
+ size = size - read_size;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * We define external_cs_manage function as 'weak' as some targets
|
|
|
|
+ * (like MSCC Ocelot) don't control the external CS pin using a GPIO
|
|
|
|
+ * controller. These SoCs use specific registers to control by
|
|
|
|
+ * software the SPI pins (and especially the CS).
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static void external_cs_manage(struct es_spi_priv *priv, bool on)
|
|
|
|
+{
|
|
|
|
+ gpiod_set_value(priv->cs_gpio, on ? 1 : 0);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* The size of ctrl1 limits data transfers to 64K */
|
|
|
|
+static int eswin_bootspi_adjust_op_size(struct spi_mem *mem,
|
|
|
|
+ struct spi_mem_op *op)
|
|
|
|
+{
|
|
|
|
+ op->data.nbytes = min(op->data.nbytes, (unsigned int)SZ_64K);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * The controller only supports Standard SPI mode, Duall mode and
|
|
|
|
+ * Quad mode. Double sanitize the ops here to avoid OOB access.
|
|
|
|
+ */
|
|
|
|
+static bool eswin_bootspi_supports_op(struct spi_mem *mem,
|
|
|
|
+ const struct spi_mem_op *op)
|
|
|
|
+{
|
|
|
|
+ return spi_mem_default_supports_op(mem, op);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int eswin_bootspi_exec_op(struct spi_mem *mem,
|
|
|
|
+ const struct spi_mem_op *op)
|
|
|
|
+{
|
|
|
|
+ bool read = op->data.dir == SPI_MEM_DATA_IN;
|
|
|
|
+ int ret = 0;
|
|
|
|
+ struct es_spi_priv *priv = spi_master_get_devdata(mem->spi->master);
|
|
|
|
+ struct device *dev = priv->dev;
|
|
|
|
+
|
|
|
|
+ priv->addr = op->addr.val;
|
|
|
|
+ priv->opcode = op->cmd.opcode;
|
|
|
|
+
|
|
|
|
+ dev_dbg(dev, "\n[%s %d]: addr=0x%llx opcode=0x%x\n", __func__,__LINE__,
|
|
|
|
+ priv->addr, priv->opcode);
|
|
|
|
+
|
|
|
|
+ if ( priv->opcode == SPINOR_OP_WREN
|
|
|
|
+ || priv->opcode == SPINOR_OP_WRDI)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ switch(priv->opcode) {
|
|
|
|
+ case SPINOR_OP_RDID:
|
|
|
|
+ case SPINOR_OP_RDSFDP:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_READ_JEDEC_ID;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_BE_4K:
|
|
|
|
+ case SPINOR_OP_BE_4K_PMC:
|
|
|
|
+ priv->opcode = SPINOR_OP_BE_4K;
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_SECTOR_ERASE;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_BE_32K:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_BLOCK_ERASE_TYPE1;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_SE:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_BLOCK_ERASE_TYPE2;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_CHIP_ERASE:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_CHIP_ERASE;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_PP:
|
|
|
|
+ case SPINOR_OP_PP_1_1_4:
|
|
|
|
+ case SPINOR_OP_PP_1_4_4:
|
|
|
|
+ case SPINOR_OP_PP_1_1_8:
|
|
|
|
+ case SPINOR_OP_PP_1_8_8:
|
|
|
|
+ priv->opcode = SPINOR_OP_PP;
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_SPI_PROGRAM;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_READ:
|
|
|
|
+ case SPINOR_OP_READ_FAST:
|
|
|
|
+ case SPINOR_OP_READ_1_1_2:
|
|
|
|
+ case SPINOR_OP_READ_1_2_2:
|
|
|
|
+ case SPINOR_OP_READ_1_1_4:
|
|
|
|
+ case SPINOR_OP_READ_1_4_4:
|
|
|
|
+ case SPINOR_OP_READ_1_1_8:
|
|
|
|
+ case SPINOR_OP_READ_1_8_8:
|
|
|
|
+ priv->opcode = SPINOR_OP_READ;
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_READ_DATA;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_RDSR:
|
|
|
|
+ case SPINOR_OP_RDSR2:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_READ_STATUS_REGISTER;
|
|
|
|
+ break;
|
|
|
|
+ case SPINOR_OP_WRSR:
|
|
|
|
+ case SPINOR_OP_WRSR2:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_WRITE_STATUS_REGISTER;
|
|
|
|
+ break;
|
|
|
|
+ case SPIC_CMD_CODE_POWER_DOWN:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_POWER_DOWN;
|
|
|
|
+ break;
|
|
|
|
+ case SPIC_CMD_CODE_RELEASE_POWER_DOWN:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_RELEASE_POWER_DOWM;
|
|
|
|
+ break;
|
|
|
|
+ case SPIC_CMD_CODE_ENABLE_RESET:
|
|
|
|
+ case SPIC_CMD_CODE_RESET:
|
|
|
|
+ priv->cmd_type = SPIC_CMD_TYPE_SPI_PROGRAM;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ dev_warn(dev, "[%s %d]: unsupport opcode = 0x%x, return sucess directly!\n",
|
|
|
|
+ __func__,__LINE__, priv->opcode);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dev_dbg(dev, "[%s %d]: data direction=%d, opcode = 0x%x, cmd_type 0x%x\n",
|
|
|
|
+ __func__,__LINE__, op->data.dir, priv->opcode, priv->cmd_type);
|
|
|
|
+ external_cs_manage(priv, false);
|
|
|
|
+
|
|
|
|
+ if (read) {
|
|
|
|
+ priv->rx = op->data.buf.in;
|
|
|
|
+ priv->len = op->data.nbytes;
|
|
|
|
+ dev_dbg(dev, "[%s %d]: read len = %u\n", __func__,__LINE__, op->data.nbytes);
|
|
|
|
+ eswin_bootspi_reader(priv);
|
|
|
|
+ } else {
|
|
|
|
+ priv->tx = op->data.buf.out;
|
|
|
|
+ priv->len = op->data.nbytes;
|
|
|
|
+ /* Fill up the write fifo before starting the transfer */
|
|
|
|
+ dev_dbg(dev, "[%s %d]: write len = 0x%x tx_addr 0x%px\n", __func__,__LINE__,
|
|
|
|
+ op->data.nbytes, priv->tx);
|
|
|
|
+ eswin_bootspi_writer(priv);
|
|
|
|
+ if (eswin_bootspi_wait_over(priv) < 0) {
|
|
|
|
+ dev_err(dev, "eswin_bootspi_wait_over ETIMEDOUT\n");
|
|
|
|
+ ret = -ETIMEDOUT;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ external_cs_manage(priv, true);
|
|
|
|
+ dev_dbg(dev, "%u bytes xfered\n", op->data.nbytes);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct spi_controller_mem_ops eswin_bootspi_mem_ops = {
|
|
|
|
+ .adjust_op_size = eswin_bootspi_adjust_op_size,
|
|
|
|
+ .supports_op = eswin_bootspi_supports_op,
|
|
|
|
+ .exec_op = eswin_bootspi_exec_op,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int eswin_bootspi_setup(struct spi_device *spi)
|
|
|
|
+{
|
|
|
|
+ struct es_spi_priv *priv = spi_master_get_devdata(spi->master);
|
|
|
|
+ struct device *dev = priv->dev;
|
|
|
|
+ int vaule = 0;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = clk_prepare_enable(priv->cfg_clk);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_err(dev, "could not enable cfg clock: %d\n", ret);
|
|
|
|
+ goto err_cfg_clk;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = clk_prepare_enable(priv->clk);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_err(dev, "could not enable clock: %d\n", ret);
|
|
|
|
+ goto err_clk;
|
|
|
|
+ }
|
|
|
|
+ /* set rate to 50M*/
|
|
|
|
+ ret = clk_set_rate(priv->clk, 50000000);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_err(dev, "could not enable clock: %d\n", ret);
|
|
|
|
+ goto err_clk;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ reset_control_deassert(priv->rstc);
|
|
|
|
+
|
|
|
|
+ /* switch bootspi to cpu mode*/
|
|
|
|
+ vaule = readl(priv->sys_regs + ES_SYSCSR_SPIMODECFG);
|
|
|
|
+ vaule |= 0x1;
|
|
|
|
+ writel(vaule, priv->sys_regs + ES_SYSCSR_SPIMODECFG);
|
|
|
|
+
|
|
|
|
+ /* Basic HW init */
|
|
|
|
+ eswin_bootspi_write(priv, ES_SPI_CSR_08, 0x0);
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+err_clk:
|
|
|
|
+ clk_disable(priv->cfg_clk);
|
|
|
|
+err_cfg_clk:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int eswin_bootspi_probe(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ struct es_spi_priv *priv;
|
|
|
|
+ struct spi_controller *master;
|
|
|
|
+ int ret = 0;
|
|
|
|
+ struct device *dev = &pdev->dev;
|
|
|
|
+
|
|
|
|
+ master = spi_alloc_master(&pdev->dev, sizeof(*priv));
|
|
|
|
+ if (!master)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ master->mode_bits = SPI_CPOL | SPI_CPHA;
|
|
|
|
+ master->flags = SPI_MASTER_HALF_DUPLEX;
|
|
|
|
+ master->setup = eswin_bootspi_setup;
|
|
|
|
+ master->dev.of_node = pdev->dev.of_node;
|
|
|
|
+ master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(16) |
|
|
|
|
+ SPI_BPW_MASK(8);
|
|
|
|
+ master->mem_ops = &eswin_bootspi_mem_ops;
|
|
|
|
+ master->num_chipselect = 1;
|
|
|
|
+
|
|
|
|
+ priv = spi_master_get_devdata(master);
|
|
|
|
+ priv->master = master;
|
|
|
|
+ priv->dev = &pdev->dev;
|
|
|
|
+ platform_set_drvdata(pdev, priv);
|
|
|
|
+
|
|
|
|
+ priv->regs = devm_platform_ioremap_resource(pdev, 0);
|
|
|
|
+ if (IS_ERR(priv->regs)) {
|
|
|
|
+ dev_err(dev, "%s %d: failed to map registers\n", __func__,__LINE__);
|
|
|
|
+ return PTR_ERR(priv->regs);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ priv->sys_regs = devm_platform_ioremap_resource(pdev, 1);
|
|
|
|
+ if (IS_ERR(priv->sys_regs)) {
|
|
|
|
+ dev_err(dev,"%s %d: failed to map sys registers\n", __func__, __LINE__);
|
|
|
|
+ return PTR_ERR(priv->sys_regs);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ priv->flash_base = devm_platform_ioremap_resource(pdev, 2);
|
|
|
|
+ if (IS_ERR(priv->flash_base)) {
|
|
|
|
+ dev_err(dev,"%s %d: failed to map sys registers\n", __func__, __LINE__);
|
|
|
|
+ return PTR_ERR(priv->flash_base);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ priv->cfg_clk = devm_clk_get(dev, "cfg_clk");
|
|
|
|
+ if (IS_ERR(priv->cfg_clk)) {
|
|
|
|
+ dev_err(dev, "%s %d:could not get cfg clk: %ld\n", __func__,__LINE__,
|
|
|
|
+ PTR_ERR(priv->cfg_clk));
|
|
|
|
+ return PTR_ERR(priv->cfg_clk);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ priv->clk = devm_clk_get(dev, "clk");
|
|
|
|
+ if (IS_ERR(priv->clk)) {
|
|
|
|
+ dev_err(dev, "%s %d:could not get clk: %ld\n",__func__,__LINE__, PTR_ERR(priv->rstc));
|
|
|
|
+ return PTR_ERR(priv->clk);
|
|
|
|
+ }
|
|
|
|
+ priv->rstc = devm_reset_control_get_optional_exclusive(dev, "rst");
|
|
|
|
+ if (IS_ERR(priv->rstc)) {
|
|
|
|
+ dev_err(dev, "%s %d:could not get rst: %ld\n", __func__,__LINE__, PTR_ERR(priv->rstc));
|
|
|
|
+ return PTR_ERR(priv->rstc);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ priv->cs_gpio = devm_gpiod_get(dev, "cs", GPIOD_OUT_LOW);
|
|
|
|
+ if (IS_ERR(priv->cs_gpio)) {
|
|
|
|
+ dev_err(dev, "%s %d: couldn't request gpio! (error %ld)\n", __func__,__LINE__,
|
|
|
|
+ PTR_ERR(priv->cs_gpio));
|
|
|
|
+ return PTR_ERR(priv->cs_gpio);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ priv->max_xfer = 32;
|
|
|
|
+ dev_info(dev, "ssi_max_xfer_size=%u\n", priv->max_xfer);
|
|
|
|
+
|
|
|
|
+ /* Currently only bits_per_word == 8 supported */
|
|
|
|
+ priv->bits_per_word = 8;
|
|
|
|
+ priv->tmode = 0; /* Tx & Rx */
|
|
|
|
+
|
|
|
|
+ if (!priv->fifo_len) {
|
|
|
|
+ priv->fifo_len = 256;
|
|
|
|
+ }
|
|
|
|
+ ret = devm_spi_register_controller(dev, master);
|
|
|
|
+ if (ret)
|
|
|
|
+ goto err_put_master;
|
|
|
|
+
|
|
|
|
+ dev_info(&pdev->dev, "fifo_len %d, %s mode.\n", priv->fifo_len, priv->irq ? "irq" : "polling");
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+err_put_master:
|
|
|
|
+ spi_master_put(master);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int eswin_bootspi_remove(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ struct es_spi_priv *priv = platform_get_drvdata(pdev);
|
|
|
|
+ struct spi_controller *master = priv->master;
|
|
|
|
+
|
|
|
|
+ spi_master_put(master);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct of_device_id eswin_bootspi_of_match[] = {
|
|
|
|
+ { .compatible = "eswin,bootspi", .data = NULL},
|
|
|
|
+ { /* end of table */}
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(of, eswin_bootspi_of_match);
|
|
|
|
+
|
|
|
|
+#ifdef CONFIG_ACPI
|
|
|
|
+static const struct acpi_device_id eswin_bootspi_acpi_match[] = {
|
|
|
|
+ {"eswin,bootspi", 0},
|
|
|
|
+ {}
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(acpi, eswin_bootspi_acpi_match);
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+static struct platform_driver eswin_bootspi_driver = {
|
|
|
|
+ .probe = eswin_bootspi_probe,
|
|
|
|
+ .remove = eswin_bootspi_remove,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "eswin-bootspi",
|
|
|
|
+ .of_match_table = eswin_bootspi_of_match,
|
|
|
|
+#ifdef CONFIG_ACPI
|
|
|
|
+ .acpi_match_table = eswin_bootspi_acpi_match,
|
|
|
|
+#endif
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_platform_driver(eswin_bootspi_driver);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Huangyifeng <huangyifeng@eswincomputing.com>");
|
|
|
|
+MODULE_DESCRIPTION("Eswin Boot SPI Controller Driver for EIC770X SoCs");
|
|
|
|
+MODULE_LICENSE("GPL v2");
|
|
|
|
--
|
|
|
|
2.47.0
|
|
|
|
|