+// Copyright (C) 2020 - 2021 Raptor Engineering, LLC
+//
+// Released under the terms of the GPL v3
+// See the LICENSE file for full details
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "console.h"
+#include "microwatt_soc.h"
+#include "io.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "micron_n25q_flash.h"
+#include "tercel_spi.h"
+
+#define ALLOW_SPI_QUAD_MODE 1
+
+static inline uint8_t read_tercel_reg8(uint8_t reg)
+{
+ return readb((unsigned long)(SPI_FCTRL_BASE+reg));
+}
+
+static inline void write_tercel_reg8(uint8_t reg, uint8_t value)
+{
+ writeb(value, (unsigned long)(SPI_FCTRL_BASE+reg));
+}
+
+static inline uint32_t read_tercel_register(uint8_t reg)
+{
+ return readl((unsigned long)(SPI_FCTRL_BASE+reg));
+}
+
+static inline void write_tercel_register(uint8_t reg, uint32_t value)
+{
+ writel(value, (unsigned long)(SPI_FCTRL_BASE+reg));
+}
+
+static uint32_t read_host_spi_flash_id(void)
+{
+ uint32_t flash_id = 0;
+
+ // Set user mode
+ write_tercel_register(TERCEL_SPI_REG_SYS_CORE_CTL1,
+ read_tercel_register(TERCEL_SPI_REG_SYS_CORE_CTL1) |
+ (TERCEL_SPI_ENABLE_USER_MODE_MASK << TERCEL_SPI_ENABLE_USER_MODE_SHIFT));
+
+ // Send Flash ID command
+ write_tercel_reg8(0, 0x9e);
+
+ // Read response
+ flash_id = (flash_id << 8) | read_tercel_reg8(0);
+ flash_id = (flash_id << 8) | read_tercel_reg8(0);
+ flash_id = (flash_id << 8) | read_tercel_reg8(0);
+ flash_id = (flash_id << 8) | read_tercel_reg8(0);
+
+ // Clear user mode
+ write_tercel_register(TERCEL_SPI_REG_SYS_CORE_CTL1,
+ read_tercel_register(TERCEL_SPI_REG_SYS_CORE_CTL1) &
+ ~(TERCEL_SPI_ENABLE_USER_MODE_MASK <<
+ TERCEL_SPI_ENABLE_USER_MODE_SHIFT));
+
+ return flash_id;
+}
+
+
+/* this is a "level 1" speed-up, which gets an initial improvement of 10-50x
+ * over the default speed (which is a scant 100 bytes per second).
+ */
+void crank_up_qspi_level1(void)
+{
+ // WARNING: KESTREL SPECIFIC
+ // Set SPI clock cycle divider to 1
+ uint32_t dword;
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_CLOCK_DIVISOR_MASK <<
+ TERCEL_SPI_PHY_CLOCK_DIVISOR_SHIFT);
+ dword |= ((1 & TERCEL_SPI_PHY_CLOCK_DIVISOR_MASK) <<
+ TERCEL_SPI_PHY_CLOCK_DIVISOR_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+ // Enable read merging
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG5);
+ dword |= (TERCEL_SPI_FLASH_EN_MULTCYC_READ_MASK <<
+ TERCEL_SPI_FLASH_EN_MULTCYC_READ_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG5, dword);
+}
+
+extern void uart_writeuint32(uint32_t val);
+
+// level 2 speedup
+int host_spi_flash_init(void)
+{
+ int i;
+ uint32_t dword;
+ uint32_t flash_device_id;
+
+ if ((read_tercel_register(TERCEL_SPI_REG_DEVICE_ID_HIGH) !=
+ TERCEL_SPI_DEVICE_ID_HIGH) ||
+ (read_tercel_register(TERCEL_SPI_REG_DEVICE_ID_LOW) !=
+ TERCEL_SPI_DEVICE_ID_LOW))
+ {
+ return -1;
+ }
+
+ uint32_t tercel_version;
+ tercel_version = read_tercel_register(TERCEL_SPI_REG_DEVICE_VERSION);
+ puts("Raptor Tercel SPI master found, device version ");
+ uart_writeuint32((tercel_version >> TERCEL_SPI_VERSION_MAJOR_SHIFT) &
+ TERCEL_SPI_VERSION_MAJOR_MASK);
+ uart_writeuint32((tercel_version >> TERCEL_SPI_VERSION_MINOR_SHIFT) &
+ TERCEL_SPI_VERSION_MINOR_MASK);
+ uart_writeuint32((tercel_version >> TERCEL_SPI_VERSION_PATCH_SHIFT) &
+ TERCEL_SPI_VERSION_PATCH_MASK);
+ puts("\n");
+
+ flash_device_id = read_host_spi_flash_id();
+ puts("Flash ID ");
+ uart_writeuint32(flash_device_id);
+ puts("\n");
+ for (i = 0; i < (sizeof(micron_n25q_spi_device_ids) /
+ sizeof(micron_n25q_spi_device_ids[0])); i++)
+ {
+ if (flash_device_id == micron_n25q_spi_device_ids[i])
+ {
+ puts(micron_n25q_spi_device_names[i]);
+ puts(" Flash device detected, configuring\n");
+
+ // Set up Flash-specific commands
+ dword = 0;
+ dword |= (MICRON_N25Q_SPI_4BA_QSPI_READ_CMD
+ & TERCEL_SPI_4BA_QSPI_CMD_MASK) <<
+ TERCEL_SPI_4BA_QSPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_3BA_QSPI_READ_CMD
+ & TERCEL_SPI_3BA_QSPI_CMD_MASK) <<
+ TERCEL_SPI_3BA_QSPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_4BA_SPI_READ_CMD &
+ TERCEL_SPI_4BA_SPI_CMD_MASK) <<
+ TERCEL_SPI_4BA_SPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_3BA_SPI_READ_CMD &
+ TERCEL_SPI_3BA_SPI_CMD_MASK) <<
+ TERCEL_SPI_3BA_SPI_CMD_SHIFT;
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG1, dword);
+
+ dword = 0;
+ dword |= (MICRON_N25Q_SPI_4BA_QSPI_FAST_READ_CMD
+ & TERCEL_SPI_4BA_QSPI_CMD_MASK) <<
+ TERCEL_SPI_4BA_QSPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_3BA_QSPI_FAST_READ_CMD
+ & TERCEL_SPI_3BA_QSPI_CMD_MASK) <<
+ TERCEL_SPI_3BA_QSPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_4BA_SPI_FAST_READ_CMD &
+ TERCEL_SPI_4BA_SPI_CMD_MASK) <<
+ TERCEL_SPI_4BA_SPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_3BA_SPI_FAST_READ_CMD &
+ TERCEL_SPI_3BA_SPI_CMD_MASK) <<
+ TERCEL_SPI_3BA_SPI_CMD_SHIFT;
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG2, dword);
+
+ dword = 0;
+ dword |= (MICRON_N25Q_SPI_4BA_QSPI_PAGE_PROGRAM_CMD
+ & TERCEL_SPI_4BA_QSPI_CMD_MASK) <<
+ TERCEL_SPI_4BA_QSPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_3BA_QSPI_PAGE_PROGRAM_CMD
+ & TERCEL_SPI_3BA_QSPI_CMD_MASK) <<
+ TERCEL_SPI_3BA_QSPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_4BA_SPI_PAGE_PROGRAM_CMD &
+ TERCEL_SPI_4BA_SPI_CMD_MASK) <<
+ TERCEL_SPI_4BA_SPI_CMD_SHIFT;
+ dword |= (MICRON_N25Q_SPI_3BA_SPI_PAGE_PROGRAM_CMD &
+ TERCEL_SPI_3BA_SPI_CMD_MASK) <<
+ TERCEL_SPI_3BA_SPI_CMD_SHIFT;
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG3, dword);
+
+ // Enable extended QSPI read/write operations
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword |= TERCEL_SPI_PHY_QSPI_EXT_READ_EN_MASK <<
+ TERCEL_SPI_PHY_QSPI_EXT_READ_EN_SHIFT;
+ dword |= TERCEL_SPI_PHY_QSPI_EXT_WRITE_EN_MASK <<
+ TERCEL_SPI_PHY_QSPI_EXT_WRITE_EN_SHIFT;
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+
+ break;
+ }
+ }
+
+ // Set SPI core to automatic mode
+ write_tercel_register(TERCEL_SPI_REG_SYS_CORE_CTL1,
+ read_tercel_register(TERCEL_SPI_REG_SYS_CORE_CTL1) &
+ ~(TERCEL_SPI_ENABLE_USER_MODE_MASK <<
+ TERCEL_SPI_ENABLE_USER_MODE_SHIFT));
+
+ // Set extra CS delay cycle count to 0
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_CS_EXTRA_IDLE_CYC_MASK <<
+ TERCEL_SPI_PHY_CS_EXTRA_IDLE_CYC_SHIFT);
+ dword |= ((0 & TERCEL_SPI_PHY_CS_EXTRA_IDLE_CYC_MASK) <<
+ TERCEL_SPI_PHY_CS_EXTRA_IDLE_CYC_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+
+ // Set maximum CS assert cycle count to 10000
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG4);
+ dword &= ~(TERCEL_SPI_FLASH_CS_EN_LIMIT_CYC_MASK <<
+ TERCEL_SPI_FLASH_CS_EN_LIMIT_CYC_SHIFT);
+ dword |= ((10000 & TERCEL_SPI_FLASH_CS_EN_LIMIT_CYC_MASK) <<
+ TERCEL_SPI_FLASH_CS_EN_LIMIT_CYC_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG4, dword);
+
+ // Set SPI fast read dummy cycles to
+ // MICRON_N25Q_SPI_FAST_READ_DUMMY_CLOCK_CYCLES
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_DUMMY_CYCLES_MASK <<
+ TERCEL_SPI_PHY_DUMMY_CYCLES_SHIFT);
+ dword |= ((MICRON_N25Q_SPI_FAST_READ_DUMMY_CLOCK_CYCLES
+ & TERCEL_SPI_PHY_DUMMY_CYCLES_MASK) <<
+ TERCEL_SPI_PHY_DUMMY_CYCLES_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+
+ // Enable SPI fast read functionality
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_FAST_READ_ENABLE_MASK <<
+ TERCEL_SPI_PHY_FAST_READ_ENABLE_SHIFT);
+ dword |= ((1 & TERCEL_SPI_PHY_FAST_READ_ENABLE_MASK) <<
+ TERCEL_SPI_PHY_FAST_READ_ENABLE_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+
+ // Set SPI controller to 4BA mode
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_4BA_ENABLE_MASK <<
+ TERCEL_SPI_PHY_4BA_ENABLE_SHIFT);
+ dword |= ((TERCEL_SPI_PHY_4BA_MODE & TERCEL_SPI_PHY_4BA_ENABLE_MASK)
+ << TERCEL_SPI_PHY_4BA_ENABLE_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+
+#if (ALLOW_SPI_QUAD_MODE)
+ // Set SPI controller to QSPI mode
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_IO_TYPE_MASK << TERCEL_SPI_PHY_IO_TYPE_SHIFT);
+ dword |= ((TERCEL_SPI_PHY_IO_TYPE_QUAD & TERCEL_SPI_PHY_IO_TYPE_MASK)
+ << TERCEL_SPI_PHY_IO_TYPE_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+#endif
+
+ // Set SPI clock cycle divider to 5
+ dword = read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1);
+ dword &= ~(TERCEL_SPI_PHY_CLOCK_DIVISOR_MASK <<
+ TERCEL_SPI_PHY_CLOCK_DIVISOR_SHIFT);
+ dword |= ((5 & TERCEL_SPI_PHY_CLOCK_DIVISOR_MASK) <<
+ TERCEL_SPI_PHY_CLOCK_DIVISOR_SHIFT);
+ write_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1, dword);
+
+ // Calculate and dump configured SPI clock speed
+ uint8_t spi_divisor =
+ (read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1)
+ >> TERCEL_SPI_PHY_CLOCK_DIVISOR_SHIFT) &
+ TERCEL_SPI_PHY_CLOCK_DIVISOR_MASK;
+ spi_divisor = (spi_divisor + 1) * 2;
+ uint8_t spi_dummy_cycles =
+ (read_tercel_register(TERCEL_SPI_REG_SYS_PHY_CFG1)
+ >> TERCEL_SPI_PHY_DUMMY_CYCLES_SHIFT) &
+ TERCEL_SPI_PHY_DUMMY_CYCLES_MASK;
+ puts("Flash controller frequency ");
+ uart_writeuint32((read_tercel_register(TERCEL_SPI_REG_SYS_CLK_FREQ) /
+ spi_divisor) / 1000000);
+ puts("\nbus frequency ");
+ uart_writeuint32(read_tercel_register(TERCEL_SPI_REG_SYS_CLK_FREQ) /
+ 1000000);
+ puts("\ndummy cycles");
+ uart_writeuint32(spi_dummy_cycles);
+ puts("\n");
+
+ // Enable read merging
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG5,
+ read_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG5) |
+ (TERCEL_SPI_FLASH_EN_MULTCYC_READ_MASK <<
+ TERCEL_SPI_FLASH_EN_MULTCYC_READ_SHIFT));
+
+ // Enable write merging
+ write_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG5,
+ read_tercel_register(TERCEL_SPI_REG_SYS_FLASH_CFG5)
+ |
+ (TERCEL_SPI_FLASH_EN_MULTCYC_WRITE_MASK <<
+ TERCEL_SPI_FLASH_EN_MULTCYC_WRITE_SHIFT));
+
+ return 0;
+}
+