/* * Copyright (c) 2017 Actions Semiconductor Co., Ltd * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief common code for SPI Flash (NOR & NAND & PSRAM) */ #include "spi_internal.h" #include "spimem.h" #define SPIMEM_CMD_READ_CHIPID 0x9f /* JEDEC ID */ #define SPIMEM_CMD_FAST_READ 0x0b /* fast read */ #define SPIMEM_CMD_FAST_READ_X2 0x3b /* data x2 */ #define SPIMEM_CMD_FAST_READ_X4 0x6b /* data x4 */ #define SPIMEM_CMD_FAST_READ_X2IO 0xbb /* addr & data x2 */ #define SPIMEM_CMD_FAST_READ_X4IO 0xeb /* addr & data x4 */ #define SPIMEM_CMD_ENABLE_WRITE 0x06 /* enable write */ #define SPIMEM_CMD_DISABLE_WRITE 0x04 /* disable write */ #define SPIMEM_CMD_WRITE_PAGE 0x02 /* write one page */ #define SPIMEM_CMD_ERASE_BLOCK 0xd8 /* 64KB erase */ #define SPIMEM_CMD_CONTINUOUS_READ_RESET 0xff /* exit quad continuous_read mode */ _nor_fun void spi_delay(void) { volatile int i = SPI_DELAY_LOOPS; while (i--) ; } _nor_fun static int spi_controller_num(struct spi_info *si) { if (si->base == (SPI0_REG_BASE)) { return 0; } else if (si->base == (SPI1_REGISTER_BASE)) { return 1; } else if (si->base == (SPI2_REG_BASE)) { return 2; }else { return 3; } } _nor_fun static void spi_set_bits(struct spi_info *si, unsigned int reg, unsigned int mask, unsigned int value) { spi_write(si, reg, (spi_read(si, reg) & ~mask) | value); } _nor_fun static void spi_reset(struct spi_info *si) { if (spi_controller_num(si) == 0) { /* SPI0 */ sys_write32(sys_read32(RMU_MRCR0) & ~(1 << MRCR0_SPI0RESET), RMU_MRCR0); spi_delay(); sys_write32(sys_read32(RMU_MRCR0) | (1 << MRCR0_SPI0RESET), RMU_MRCR0); } else if (spi_controller_num(si) == 1) { /* SPI1 */ sys_write32(sys_read32(RMU_MRCR0) & ~(1 << MRCR0_SPI1RESET), RMU_MRCR0); spi_delay(); sys_write32(sys_read32(RMU_MRCR0) | (1 << MRCR0_SPI1RESET), RMU_MRCR0); } else if (spi_controller_num(si) == 2) { /* SPI2 */ sys_write32(sys_read32(RMU_MRCR0) & ~(1 << MRCR0_SPI2RESET), RMU_MRCR0); spi_delay(); sys_write32(sys_read32(RMU_MRCR0) | (1 << MRCR0_SPI2RESET), RMU_MRCR0); }else { /* SPI3 */ sys_write32(sys_read32(RMU_MRCR0) & ~(1 << MRCR0_SPI3RESET), RMU_MRCR0); spi_delay(); sys_write32(sys_read32(RMU_MRCR0) | (1 << MRCR0_SPI3RESET), RMU_MRCR0); } } #if 0 _nor_fun static void spi_init_clk(struct spi_info *si) { if (spi_controller_num(si) == 0) { /* SPI0 clock source: 32M HOSC, div 2*/ sys_write32(0x01, CMU_SPI0CLK); /* enable SPI0 module clock */ sys_write32(sys_read32(CMU_DEVCLKEN0) | (1 << CMU_DEVCLKEN0_SPI0CLKEN), CMU_DEVCLKEN0); } else if (spi_controller_num(si) == 1) { /* SPI1 clock source: 32M HOSC div 2*/ sys_write32(0x01, CMU_SPI1CLK); /* enable SPI1 module clock */ sys_write32(sys_read32(CMU_DEVCLKEN0) | (1 << CMU_DEVCLKEN0_SPI1CLKEN), CMU_DEVCLKEN0); } else if (spi_controller_num(si) == 2) { /* SPI2 clock source: 32M HOSC div 2*/ sys_write32(0x01, CMU_SPI2CLK); /* enable SPI0 module clock */ sys_write32(sys_read32(CMU_DEVCLKEN0) | (1 << CMU_DEVCLKEN0_SPI2CLKEN), CMU_DEVCLKEN0); }else { /* SPI3 clock source: 32M HOSC div 2*/ sys_write32(0x01, CMU_SPI3CLK); /* enable SPI0 module clock */ sys_write32(sys_read32(CMU_DEVCLKEN0) | (1 << CMU_DEVCLKEN0_SPI3CLKEN), CMU_DEVCLKEN0); } } #endif _nor_fun static void spi_set_cs(struct spi_info *si, int value) { if (si->set_cs) si->set_cs(si, value); else spi_set_bits(si, SSPI_CTL, SSPI_CTL_SS, value ? SSPI_CTL_SS : 0); } _nor_fun static void spi_setup_bus_width(struct spi_info *si, unsigned char bus_width) { spi_set_bits(si, SSPI_CTL, SSPI_CTL_IO_MODE_MASK, ((bus_width & 0x7) / 2 + 1) << SSPI_CTL_IO_MODE_SHIFT); spi_delay(); } _nor_fun static void spi_setup_randmomize(struct spi_info *si, int is_tx, int enable) { if (enable) spi_set_bits(si, SSPI_CTL, SSPI_CTL_RAND_TXEN | SSPI_CTL_RAND_RXEN, is_tx ? SSPI_CTL_RAND_TXEN : SSPI_CTL_RAND_RXEN); else spi_set_bits(si, SSPI_CTL, SSPI_CTL_RAND_TXEN | SSPI_CTL_RAND_RXEN, 0); } _nor_fun static void spi_pause_resume_randmomize(struct spi_info *si, int is_pause) { spi_set_bits(si, SSPI_CTL, SSPI_CTL_RAND_PAUSE, is_pause ? SSPI_CTL_RAND_PAUSE : 0); } _nor_fun static int spi_is_randmomize_pause(struct spi_info *si) { return !!(spi_read(si, SSPI_CTL) & SSPI_CTL_RAND_PAUSE); } _nor_fun static void spi_wait_tx_complete(struct spi_info *si) { if (spi_controller_num(si) == 0) { /* SPI0 */ while (!(spi_read(si, SSPI_STATUS) & SSPI_STATUS_TX_EMPTY)) ; /* wait until tx fifo is empty */ while ((spi_read(si, SSPI_STATUS) & SSPI_STATUS_BUSY)) ; } else { /* SPI1 & SPI2 & SPI3 */ while (!(spi_read(si, SSPI_STATUS) & SSPI1_STATUS_TX_EMPTY)) ; /* wait until tx fifo is empty */ while ((spi_read(si, SSPI_STATUS) & SSPI1_STATUS_BUSY)) ; } } _nor_fun static void spi_read_data(struct spi_info *si, unsigned char *buf, int len) { spi_write(si, SSPI_BC, len); /* switch to read mode */ spi_set_bits(si, SSPI_CTL, SSPI_CTL_WR_MODE_MASK, SSPI_CTL_WR_MODE_READ); /* read data */ while (len--) { if (spi_controller_num(si) == 0) { /* SPI0 */ while (spi_read(si, SSPI_STATUS) & SSPI_STATUS_RX_EMPTY) ; } else { /* SPI1 & SPI2 & SPI3 */ while (spi_read(si, SSPI_STATUS) & SSPI1_STATUS_RX_EMPTY) ; } *buf++ = spi_read(si, SSPI_RXDAT); } /* disable read mode */ spi_set_bits(si, SSPI_CTL, SSPI_CTL_WR_MODE_MASK, SSPI_CTL_WR_MODE_DISABLE); } _nor_fun static void spi_write_data(struct spi_info *si, const unsigned char *buf, int len) { /* switch to write mode */ spi_set_bits(si, SSPI_CTL, SSPI_CTL_WR_MODE_MASK, SSPI_CTL_WR_MODE_WRITE); /* write data */ while (len--) { if (spi_controller_num(si) == 0) { /* SPI0 */ while (spi_read(si, SSPI_STATUS) & SSPI_STATUS_TX_FULL) ; } else { /* SPI1 & SPI2 & SPI3 */ while (spi_read(si, SSPI_STATUS) & SSPI1_STATUS_TX_FULL) ; } spi_write(si, SSPI_TXDAT, *buf++); } spi_delay(); spi_wait_tx_complete(si); /* disable write mode */ spi_set_bits(si, SSPI_CTL, SSPI_CTL_WR_MODE_MASK, SSPI_CTL_WR_MODE_DISABLE); } #define DMA_CTL (0x00) #define DMA_START (0x04) #define DMA_SADDR (0x08) #define DMA_DADDR (0x10) #define DMA_BC (0x18) #define DMA_RC (0x1c) //#define DMA_PD (0x04) _nor_fun static void spi_read_data_by_dma(struct spi_info *si, const unsigned char *buf, int len) { spi_write(si, SSPI_BC, len); /* switch to dma read mode */ spi_set_bits(si, SSPI_CTL, SSPI_CTL_CLK_SEL_MASK | SSPI_CTL_RX_DRQ_EN | SSPI_CTL_WR_MODE_MASK, SSPI_CTL_CLK_SEL_DMA | SSPI_CTL_RX_DRQ_EN | SSPI_CTL_WR_MODE_READ); if (spi_controller_num(si) == 0) { /* SPI0 */ sys_write32(0x200087, si->dma_base + DMA_CTL); } else if (spi_controller_num(si) == 1) { /* SPI1 */ sys_write32(0x200088, si->dma_base + DMA_CTL); } else if (spi_controller_num(si) == 2) { /* SPI2 */ sys_write32(0x200089, si->dma_base + DMA_CTL); } else { /* SPI3 */ sys_write32(0x20008a, si->dma_base + DMA_CTL); } sys_write32(si->base + SSPI_RXDAT, si->dma_base + DMA_SADDR); sys_write32((unsigned int)buf, si->dma_base + DMA_DADDR); sys_write32(len, si->dma_base + DMA_BC); /* start dma */ sys_write32(1, si->dma_base + DMA_START); while (sys_read32(si->dma_base + DMA_START) & 0x1) { /* wait */ } spi_delay(); spi_wait_tx_complete(si); spi_set_bits(si, SSPI_CTL, SSPI_CTL_CLK_SEL_MASK | SSPI_CTL_RX_DRQ_EN | SSPI_CTL_WR_MODE_MASK, SSPI_CTL_CLK_SEL_CPU | SSPI_CTL_WR_MODE_DISABLE); } _nor_fun static void spi_write_data_by_dma(struct spi_info *si, const unsigned char *buf, int len) { /* switch to dma write mode */ spi_set_bits(si, SSPI_CTL, SSPI_CTL_CLK_SEL_MASK | SSPI_CTL_TX_DRQ_EN | SSPI_CTL_WR_MODE_MASK, SSPI_CTL_CLK_SEL_DMA | SSPI_CTL_TX_DRQ_EN | SSPI_CTL_WR_MODE_WRITE); if (spi_controller_num(si) == 0) { /* SPI0 */ sys_write32(0x208700, si->dma_base + DMA_CTL); } else if (spi_controller_num(si) == 1) { /* SPI1 */ sys_write32(0x208800, si->dma_base + DMA_CTL); } else if (spi_controller_num(si) == 2) { /* SPI2 */ sys_write32(0x208900, si->dma_base + DMA_CTL); } else { /* SPI3 */ sys_write32(0x208a00, si->dma_base + DMA_CTL); } sys_write32((unsigned int)buf, si->dma_base + DMA_SADDR); sys_write32(si->base + SSPI_TXDAT, si->dma_base + DMA_DADDR); sys_write32(len, si->dma_base + DMA_BC); /* start dma */ sys_write32(1, si->dma_base + DMA_START); while (sys_read32(si->dma_base + DMA_START) & 0x1) { /* wait */ } spi_delay(); spi_wait_tx_complete(si); spi_set_bits(si, SSPI_CTL, SSPI_CTL_CLK_SEL_MASK | SSPI_CTL_TX_DRQ_EN | SSPI_CTL_WR_MODE_MASK, SSPI_CTL_CLK_SEL_CPU | SSPI_CTL_WR_MODE_DISABLE); } _nor_fun static void sys_memcpy_swap(void *dst, const void *src, int length) { unsigned char *tmp_src = (unsigned char *)((unsigned int)src + (length - 1)); unsigned char *tmp_dst = (unsigned char *)dst; for (; length > 0; length--) { *tmp_dst++ = *tmp_src--; } } _nor_fun static void _spimem_read_data(struct spi_info *si, void *buf, int len) { if ((len > 16) && si->dma_base) { spi_read_data_by_dma(si, (unsigned char *)buf, len); } else { spi_read_data(si, (unsigned char *)buf, len); } } _nor_fun static void _spimem_write_data(struct spi_info *si, const void *buf, int len) { if ((len > 16) && si->dma_base) { spi_write_data_by_dma(si, (const unsigned char *)buf, len); } else { spi_write_data(si, (const unsigned char *)buf, len); } } _nor_fun static void _spimem_write_byte(struct spi_info *si, unsigned char byte) { _spimem_write_data(si, &byte, 1); } _nor_fun void spimem_set_cs(struct spi_info *si, int value) { spi_set_cs(si, value); } _nor_fun void spimem_continuous_read_reset(struct spi_info *si) { spimem_set_cs(si, 0); _spimem_write_byte(si, SPIMEM_CMD_CONTINUOUS_READ_RESET); _spimem_write_byte(si, SPIMEM_CMD_CONTINUOUS_READ_RESET); spimem_set_cs(si, 1); } _nor_fun unsigned int spimem_prepare_op(struct spi_info *si) { unsigned int orig_spi_ctl; unsigned int use_3wire = 0; /* backup old SPI_CTL */ orig_spi_ctl = spi_read(si, SSPI_CTL); if (!spi_is_randmomize_pause(si)) spi_reset(si); if (spi_controller_num(si) == 0) { spi_write(si, SSPI_CTL, (orig_spi_ctl & SSPI_CTL_RAND_MASK) | SSPI_CTL_AHB_REQ | SSPI_CTL_IO_MODE_1X | SSPI_CTL_WR_MODE_DISABLE | SSPI_CTL_RX_FIFO_EN | SSPI_CTL_TX_FIFO_EN | SSPI_CTL_SS | 8 << SSPI_CTL_DELAYCHAIN_SHIFT); use_3wire = orig_spi_ctl & SSPI_CTL_SPI_3WIRE; } else { spi_write(si, SSPI_CTL, SSPI1_CTL_AHB_REQ | SSPI_CTL_IO_MODE_1X | SSPI_CTL_WR_MODE_DISABLE | SSPI_CTL_RX_FIFO_EN | SSPI_CTL_TX_FIFO_EN | SSPI_CTL_SS | 8 << SSPI_CTL_DELAYCHAIN_SHIFT); } if (use_3wire) spi_set_bits(si, SSPI_CTL, SSPI_CTL_SPI_3WIRE, SSPI_CTL_SPI_3WIRE); if (si->delay_chain != 0xff) spi_set_bits(si, SSPI_CTL, SSPI_CTL_DELAYCHAIN_MASK, si->delay_chain << SSPI_CTL_DELAYCHAIN_SHIFT); if (si->flag & SPI_FLAG_SPI_MODE0) { if (spi_controller_num(si) == 0) { spi_set_bits(si, SSPI_CTL, SSPI_CTL_MODE_MASK, SSPI_CTL_MODE_MODE0); } else { spi_set_bits(si, SSPI_CTL, SSPI1_CTL_MODE_MASK, SSPI1_CTL_MODE_MODE0); } } if (si->set_clk && si->freq_khz) si->set_clk(si, si->freq_khz); if (si->prepare_hook) si->prepare_hook(si); return orig_spi_ctl; } _nor_fun void spimem_complete_op(struct spi_info *si, unsigned int orig_spi_ctl) { /* restore old SPI_CTL */ spi_write(si, SSPI_CTL, orig_spi_ctl); spi_delay(); } _nor_fun int spimem_transfer(struct spi_info *si, unsigned char cmd, unsigned int addr, int addr_len, void *buf, int length, unsigned char dummy_len, unsigned int flag) { unsigned int orig_spi_ctl, i, addr_be; unsigned int key = 0; /* address to big endian */ if (addr_len > 0) sys_memcpy_swap(&addr_be, &addr, addr_len); if (!(si->flag & SPI_FLAG_NO_IRQ_LOCK)) { key = irq_lock(); } orig_spi_ctl = spimem_prepare_op(si); if (!spi_is_randmomize_pause(si)) spi_setup_randmomize(si, 0, 0); #ifdef NORDEBUG // SPI_SEED configuration, nor reset sys_write32(0x12345678, SPI0_SEED); #endif spimem_set_cs(si, 0); /* cmd & address & data all use multi io mode */ if (flag & SPIMEM_TFLAG_MIO_CMD_ADDR_DATA) spi_setup_bus_width(si, si->bus_width); /* write command */ _spimem_write_byte(si, cmd); /* address & data use multi io mode */ if (flag & SPIMEM_TFLAG_MIO_ADDR_DATA) spi_setup_bus_width(si, si->bus_width); if (addr_len > 0) _spimem_write_data(si, &addr_be, addr_len); /* send dummy bytes */ for (i = 0; i < dummy_len; i++) _spimem_write_byte(si, 0); /* only data use multi io mode */ if (flag & SPIMEM_TFLAG_MIO_DATA) spi_setup_bus_width(si, si->bus_width); /* send or get data */ if (length > 0) { if (flag & SPIMEM_TFLAG_WRITE_DATA) { if (flag & SPIMEM_TFLAG_RESUME_RANDOMIZE) spi_pause_resume_randmomize(si, 0); if (flag & SPIMEM_TFLAG_ENABLE_RANDOMIZE) spi_setup_randmomize(si, 1, 1); _spimem_write_data(si, buf, length); } else { if (flag & SPIMEM_TFLAG_RESUME_RANDOMIZE) spi_pause_resume_randmomize(si, 0); if (flag & SPIMEM_TFLAG_ENABLE_RANDOMIZE) spi_setup_randmomize(si, 0, 1); _spimem_read_data(si, buf, length); } } /* restore spi bus width to 1-bit */ if (flag & SPIMEM_TFLAG_MIO_MASK) spi_setup_bus_width(si, 1); if (flag & SPIMEM_TFLAG_PAUSE_RANDOMIZE) spi_pause_resume_randmomize(si, 1); else if (flag & SPIMEM_TFLAG_ENABLE_RANDOMIZE) spi_setup_randmomize(si, 0, 0); spimem_set_cs(si, 1); if (!spi_is_randmomize_pause(si)) spimem_complete_op(si, orig_spi_ctl); if (!(si->flag & SPI_FLAG_NO_IRQ_LOCK)) { irq_unlock(key); } return 0; } _nor_fun int spimem_write_cmd_addr(struct spi_info *si, unsigned char cmd, unsigned int addr, int addr_len) { return spimem_transfer(si, cmd, addr, addr_len, 0, 0, 0, 0); } _nor_fun int spimem_write_cmd(struct spi_info *si, unsigned char cmd) { return spimem_write_cmd_addr(si, cmd, 0, 0); } _nor_fun void spimem_read_chipid(struct spi_info *si, void *chipid, int len) { spimem_transfer(si, SPIMEM_CMD_READ_CHIPID, 0, 0, chipid, len, 0, 0); } _nor_fun unsigned char spimem_read_status(struct spi_info *si, unsigned char cmd) { unsigned int status; spimem_transfer(si, cmd, 0, 0, &status, 1, 0, 0); return status; } _nor_fun void spimem_set_write_protect(struct spi_info *si, int protect) { if (protect) spimem_write_cmd(si, SPIMEM_CMD_DISABLE_WRITE); else spimem_write_cmd(si, SPIMEM_CMD_ENABLE_WRITE); } _nor_fun void spimem_read_page(struct spi_info *si, unsigned int addr, int addr_len, void *buf, int len) { unsigned char cmd; unsigned int flag, dlen =1; if (si->bus_width == 4) { if(si->flag & SPI_FLAG_SPI_NXIO) { cmd = SPIMEM_CMD_FAST_READ_X4IO; flag = SPIMEM_TFLAG_MIO_ADDR_DATA; dlen = 3;/*1byte mode & 2byte dummy*/ }else{ cmd = SPIMEM_CMD_FAST_READ_X4; flag = SPIMEM_TFLAG_MIO_DATA; } } else if (si->bus_width == 2) { if(si->flag & SPI_FLAG_SPI_NXIO){ cmd = SPIMEM_CMD_FAST_READ_X2IO; flag = SPIMEM_TFLAG_MIO_ADDR_DATA; }else { cmd = SPIMEM_CMD_FAST_READ_X2; flag = SPIMEM_TFLAG_MIO_DATA; } } else { cmd = SPIMEM_CMD_FAST_READ; flag = 0; } /* change to 4 bytes address commands*/ if (addr_len == 4) cmd++; spimem_transfer(si, cmd, addr, addr_len, buf, len, dlen, flag); } _nor_fun int spimem_erase_block(struct spi_info *si, unsigned int addr) { spimem_set_write_protect(si, 0); spimem_write_cmd_addr(si, SPIMEM_CMD_ERASE_BLOCK, addr, 3); return 0; }