From 1e39274601566d11c1cb94a9032a425c699e4874 Mon Sep 17 00:00:00 2001 From: Piotr Dymacz Date: Sun, 22 Nov 2015 22:51:58 +0100 Subject: [PATCH] Add initial version of new QC/A SPI FLASH driver Instead of using so called bit banging/blasting mode, as the old one driver from Atheros SDK does, this driver utilizes SPI shift registers. With this approach, we need to only setup one register and let the built-in SPI controller do the rest. SPI shift registers and how they work exactly are not described very well in QC/A WiSoCs datasheets, so som things had to be tested in practice. This driver has been already successfully tested on all supported platforms, including: - Atheros AR933x (SPI shift registers are not mentioned in its datasheet!) - Atheros AR9341/4 - Qualcomm Atheros QCA953x Tests on scope showed that with this driver, clock for SPI is the same as the one set in SPI_CONTROL register (depends on platform, between 25 and 30 MHz). With old driver, clock was around 4-6 MHz, which means that new driver may drastically increase SPI operations speed. In practical tests, for operations like sector erase, time needed by chip to finish the operation, is more significant, so there is no difference at all. Comparison for other operations, like page program, has not been done yet. --- u-boot/cpu/mips/ar7240/qca_sf.c | 205 ++++++++++++++++++++++++++++ u-boot/include/soc/qca_soc_common.h | 19 +++ 2 files changed, 224 insertions(+) create mode 100644 u-boot/cpu/mips/ar7240/qca_sf.c diff --git a/u-boot/cpu/mips/ar7240/qca_sf.c b/u-boot/cpu/mips/ar7240/qca_sf.c new file mode 100644 index 0000000..3b48e91 --- /dev/null +++ b/u-boot/cpu/mips/ar7240/qca_sf.c @@ -0,0 +1,205 @@ +/* + * Qualcomm/Atheros Serial SPI FLASH driver utilizing SHIFT registers + * + * Copyright (C) 2015 Piotr Dymacz + * + * SPDX-License-Identifier:GPL-2.0 + */ + +#include +#include +#include + +/* Basic SPI FLASH commands */ +#define SPI_FLASH_CMD_WRSR 0x01 +#define SPI_FLASH_CMD_PP 0x02 +#define SPI_FLASH_CMD_READ 0x03 +#define SPI_FLASH_CMD_WRDI 0x04 +#define SPI_FLASH_CMD_RDSR 0x05 +#define SPI_FLASH_CMD_WREN 0x06 + +/* SPI FLASH erase related commands */ +#define SPI_FLASH_CMD_ES_4KB 0x20 +#define SPI_FLASH_CMD_ES_32KB 0x52 +#define SPI_FLASH_CMD_ES_64KB 0xD8 +#define SPI_FLASH_CMD_ES_ALL 0xC7 + +/* Other SPI FLASH commands */ +#define SPI_FLASH_CMD_JEDEC 0x9F + +/* Use CS0 by default */ +static u32 qca_sf_cs_mask = QCA_SPI_SHIFT_CNT_CHNL_CS0_MASK; + +static inline void qca_sf_spi_en(void) +{ + qca_soc_reg_write(QCA_SPI_FUNC_SEL_REG, 1); +} + +static inline void qca_sf_spi_di(void) +{ + qca_soc_reg_write(QCA_SPI_SHIFT_CNT_REG, 0); + qca_soc_reg_write(QCA_SPI_FUNC_SEL_REG, 0); +} + +static inline u32 qca_sf_shift_in(void) +{ + return qca_soc_reg_read(QCA_SPI_SHIFT_DATAIN_REG); +} + +/* + * Shifts out 'bits_cnt' bits from 'data_out' value + * If 'terminate' is zero, then CS is not driven high at end of transaction + */ +static void qca_sf_shift_out(u32 data_out, u32 bits_cnt, u32 terminate) +{ + u32 reg_val = 0; + + qca_soc_reg_write(QCA_SPI_SHIFT_CNT_REG, 0); + + /* Data to shift out */ + qca_soc_reg_write(QCA_SPI_SHIFT_DATAOUT_REG, data_out); + + reg_val = reg_val | bits_cnt + | qca_sf_cs_mask + | QCA_SPI_SHIFT_CNT_SHIFT_EN_MASK; + + if (terminate) + reg_val = reg_val | QCA_SPI_SHIFT_CNT_TERMINATE_MASK; + + /* Enable shifting in/out */ + qca_soc_reg_write(QCA_SPI_SHIFT_CNT_REG, reg_val); +} + +static inline void qca_sf_write_en(void) +{ + qca_sf_shift_out(SPI_FLASH_CMD_WREN, 8, 1); +} + +static inline void qca_sf_write_di(void) +{ + qca_sf_shift_out(SPI_FLASH_CMD_WRDI, 8, 1); +} + +static void qca_sf_bank_to_cs_mask(u32 bank) +{ + switch (bank) { + case 0: + qca_sf_cs_mask = QCA_SPI_SHIFT_CNT_CHNL_CS0_MASK; + break; + case 1: + qca_sf_cs_mask = QCA_SPI_SHIFT_CNT_CHNL_CS1_MASK; + break; + case 2: + qca_sf_cs_mask = QCA_SPI_SHIFT_CNT_CHNL_CS2_MASK; + break; + default: + qca_sf_cs_mask = QCA_SPI_SHIFT_CNT_CHNL_CS0_MASK; + break; + } +} + +/* Poll status register and wait till busy bit is cleared */ +static void qca_sf_busy_wait(void) +{ + volatile u32 data_in; + + /* Poll status register continuously (keep CS low during whole loop) */ + qca_sf_shift_out(SPI_FLASH_CMD_RDSR, 8, 0); + + do { + qca_sf_shift_out(0x0, 8, 0); + data_in = qca_sf_shift_in() & 0x1; + } while (data_in); + + /* Disable CS chip */ + qca_sf_shift_out(0x0, 0, 1); +} + +/* Bulk (whole) FLASH erase */ +void qca_sf_bulk_erase(u32 bank) +{ + qca_sf_bank_to_cs_mask(bank); + qca_sf_spi_en(); + qca_sf_write_en(); + qca_sf_shift_out(SPI_FLASH_CMD_ES_ALL, 8, 1); + qca_sf_busy_wait(); + qca_sf_spi_di(); +} + +/* Erase one sector at provided address */ +u32 qca_sf_sect_erase(u32 bank, u32 address, u32 sect_size) +{ + u32 data_out; + + qca_sf_bank_to_cs_mask(bank); + + switch (sect_size) { + case 4 * 1024: + data_out = SPI_FLASH_CMD_ES_4KB << 24; + break; + case 32 * 1024: + data_out = SPI_FLASH_CMD_ES_32KB << 24; + break; + case 64 * 1024: + data_out = SPI_FLASH_CMD_ES_64KB << 24; + break; + default: + return 1; + } + + /* TODO: 4-byte addressing support */ + data_out = data_out | (address & 0x00FFFFFF); + + qca_sf_spi_en(); + qca_sf_write_en(); + qca_sf_shift_out(data_out, 32, 1); + qca_sf_busy_wait(); + qca_sf_spi_di(); + + return 0; +} + +/* Writes 'length' bytes at 'address' using page program command */ +void qca_sf_write_page(u32 bank, u32 address, u32 length, u8 *data) +{ + u32 data_out, i; + + qca_sf_bank_to_cs_mask(bank); + + data_out = SPI_FLASH_CMD_PP << 24; + data_out = data_out | (address & 0x00FFFFFF); + + qca_sf_spi_en(); + qca_sf_write_en(); + qca_sf_shift_out(data_out, 32, 0); + + length--; + for (i = 0; i < length; i++) { + qca_sf_shift_out(*(data + i), 8, 0); + } + + /* Last byte and terminate */ + qca_sf_shift_out(*(data + i), 8, 1); + + qca_sf_busy_wait(); + qca_sf_spi_di(); +} + +/* Returns JEDEC ID for selected FLASH chip */ +u32 qca_sf_jedec_id(u32 bank) +{ + volatile u32 data_in = 0; + + qca_sf_bank_to_cs_mask(bank); + + qca_sf_spi_en(); + qca_sf_shift_out(SPI_FLASH_CMD_JEDEC << 24, 32, 1); + + do { + data_in = qca_sf_shift_in(); + } while (data_in == 0); + + qca_sf_spi_di(); + + return (data_in & 0x00FFFFFF); +} diff --git a/u-boot/include/soc/qca_soc_common.h b/u-boot/include/soc/qca_soc_common.h index f81675f..5df014c 100644 --- a/u-boot/include/soc/qca_soc_common.h +++ b/u-boot/include/soc/qca_soc_common.h @@ -1224,6 +1224,21 @@ #define QCA_SPI_IO_CTRL_IO_CS2_SHIFT 18 #define QCA_SPI_IO_CTRL_IO_CS2_MASK (1 << QCA_SPI_IO_CTRL_IO_CS2_SHIFT) +/* SPI_SHIFT_CNT_ADDR register (SPI content to shift out or in) */ +#define QCA_SPI_SHIFT_CNT_BITS_CNT_SHIFT 0 +#define QCA_SPI_SHIFT_CNT_BITS_CNT_MASK BITS(QCA_SPI_SHIFT_CNT_BITS_CNT_SHIFT, 7) +#define QCA_SPI_SHIFT_CNT_TERMINATE_SHIFT 26 +#define QCA_SPI_SHIFT_CNT_TERMINATE_MASK (1 << QCA_SPI_SHIFT_CNT_TERMINATE_SHIFT) +#define QCA_SPI_SHIFT_CNT_CLKOUT_INIT_SHIFT 27 +#define QCA_SPI_SHIFT_CNT_CLKOUT_INIT_MASK (1 << QCA_SPI_SHIFT_CNT_CLKOUT_INIT_SHIFT) +#define QCA_SPI_SHIFT_CNT_CHNL_CS0_SHIFT 28 +#define QCA_SPI_SHIFT_CNT_CHNL_CS0_MASK (1 << QCA_SPI_SHIFT_CNT_CHNL_CS0_SHIFT) +#define QCA_SPI_SHIFT_CNT_CHNL_CS1_SHIFT 29 +#define QCA_SPI_SHIFT_CNT_CHNL_CS1_MASK (1 << QCA_SPI_SHIFT_CNT_CHNL_CS1_SHIFT) +#define QCA_SPI_SHIFT_CNT_CHNL_CS2_SHIFT 30 +#define QCA_SPI_SHIFT_CNT_CHNL_CS2_MASK (1 << QCA_SPI_SHIFT_CNT_CHNL_CS2_SHIFT) +#define QCA_SPI_SHIFT_CNT_SHIFT_EN_SHIFT 31 +#define QCA_SPI_SHIFT_CNT_SHIFT_EN_MASK (1 << QCA_SPI_SHIFT_CNT_SHIFT_EN_SHIFT) /* @@ -1235,6 +1250,10 @@ inline u32 qca_mem_type(void); void qca_soc_name_rev(char *buf); void qca_full_chip_reset(void); void qca_sys_clocks(u32 *cpu_clk, u32 *ddr_clk, u32 *ahb_clk, u32 *spi_clk, u32 *ref_clk); +void qca_sf_bulk_erase(u32 bank); +void qca_sf_write_page(u32 bank, u32 address, u32 length, u8 *data); +u32 qca_sf_sect_erase(u32 bank, u32 address, u32 sect_size); +u32 qca_sf_jedec_id(u32 bank); #endif /* !__ASSEMBLY__ */ /* -- 2.25.1