Add initial version of new QC/A SPI FLASH driver
authorPiotr Dymacz <pepe2k@gmail.com>
Sun, 22 Nov 2015 21:51:58 +0000 (22:51 +0100)
committerPiotr Dymacz <pepe2k@gmail.com>
Sun, 22 Nov 2015 21:51:58 +0000 (22:51 +0100)
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 [new file with mode: 0644]
u-boot/include/soc/qca_soc_common.h

diff --git a/u-boot/cpu/mips/ar7240/qca_sf.c b/u-boot/cpu/mips/ar7240/qca_sf.c
new file mode 100644 (file)
index 0000000..3b48e91
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Qualcomm/Atheros Serial SPI FLASH driver utilizing SHIFT registers
+ *
+ * Copyright (C) 2015 Piotr Dymacz <piotr@dymacz.pl>
+ *
+ * SPDX-License-Identifier:GPL-2.0
+ */
+
+#include <config.h>
+#include <asm/addrspace.h>
+#include <soc/qca_soc_common.h>
+
+/* 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);
+}
index f81675fd2347a9b6fcb41e30c13fb626d23961ec..5df014c17cbd0c559610e4c937a03ac9f3463240 100644 (file)
 #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__ */
 
 /*