/* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include #include #include #define CACHE_LINE_SIZE 64 static int curr_desc_idx = -1; struct cmd_desc { uint32_t data_address; uint32_t next_descriptor; uint32_t direction:1; uint32_t multi_io_mode:3; uint32_t reserved1:4; uint32_t fragment:1; uint32_t reserved2:7; uint32_t length:16; //------------------------// uint32_t bounce_src; uint32_t bounce_dst; uint32_t bounce_length; uint64_t padding[5]; }; enum qspi_mode { SDR_1BIT = 1, SDR_2BIT = 2, SDR_4BIT = 3, DDR_1BIT = 5, DDR_2BIT = 6, DDR_4BIT = 7, }; enum cs_state { CS_DEASSERT, CS_ASSERT }; struct xfer_cfg { enum qspi_mode mode; }; enum bus_xfer_direction { MASTER_READ = 0, MASTER_WRITE = 1, }; struct { struct cmd_desc descriptors[3]; uint8_t buffers[3][CACHE_LINE_SIZE]; } *dma = (void *)_dma_coherent; static void dma_transfer_chain(struct cmd_desc *chain) { uint32_t mstr_int_status; write32(&qcom_qspi->mstr_int_sts, 0xFFFFFFFF); write32(&qcom_qspi->next_dma_desc_addr, (uint32_t)(uintptr_t) chain); while (1) { mstr_int_status = read32(&qcom_qspi->mstr_int_sts); if (mstr_int_status & DMA_CHAIN_DONE) break; } } static void flush_chain(void) { struct cmd_desc *desc = &dma->descriptors[0]; uint8_t *src; uint8_t *dst; dma_transfer_chain(desc); while (desc) { if (desc->direction == MASTER_READ) { if (desc->bounce_length == 0) dcache_invalidate_by_mva( (void *)(uintptr_t) desc->data_address, desc->length); else { src = (void *)(uintptr_t) desc->bounce_src; dst = (void *)(uintptr_t) desc->bounce_dst; memcpy(dst, src, desc->bounce_length); } } desc = (void *)(uintptr_t) desc->next_descriptor; } curr_desc_idx = -1; } static struct cmd_desc *allocate_descriptor(void) { struct cmd_desc *current; struct cmd_desc *next; uint8_t index; current = (curr_desc_idx == -1) ? NULL : &dma->descriptors[curr_desc_idx]; index = ++curr_desc_idx; next = &dma->descriptors[index]; next->data_address = (uint32_t) (uintptr_t) dma->buffers[index]; next->next_descriptor = 0; next->direction = MASTER_READ; next->multi_io_mode = 0; next->reserved1 = 0; /* * QSPI controller doesn't support transfer starts with read segment. * So to support read transfers that are not preceded by write, set * transfer fragment bit = 1 */ next->fragment = 1; next->reserved2 = 0; next->length = 0; next->bounce_src = 0; next->bounce_dst = 0; next->bounce_length = 0; if (current) current->next_descriptor = (uint32_t)(uintptr_t) next; return next; } static void cs_change(enum cs_state state) { gpio_set(QSPI_CS, state == CS_DEASSERT); } static void configure_gpios(void) { gpio_output(QSPI_CS, 1); gpio_configure(QSPI_DATA_0, GPIO_FUNC_QSPI_DATA_0, GPIO_NO_PULL, GPIO_2MA, GPIO_OUTPUT); gpio_configure(QSPI_DATA_1, GPIO_FUNC_QSPI_DATA_1, GPIO_NO_PULL, GPIO_2MA, GPIO_OUTPUT); gpio_configure(QSPI_CLK, GPIO_FUNC_QSPI_CLK, GPIO_NO_PULL, GPIO_8MA, GPIO_OUTPUT); } static void queue_bounce_data(uint8_t *data, uint32_t data_bytes, enum qspi_mode data_mode, bool write) { struct cmd_desc *desc; uint8_t *ptr; desc = allocate_descriptor(); desc->direction = write; desc->multi_io_mode = data_mode; ptr = (void *)(uintptr_t) desc->data_address; if (write) { memcpy(ptr, data, data_bytes); } else { desc->bounce_src = (uint32_t)(uintptr_t) ptr; desc->bounce_dst = (uint32_t)(uintptr_t) data; desc->bounce_length = data_bytes; } desc->length = data_bytes; } static void queue_direct_data(uint8_t *data, uint32_t data_bytes, enum qspi_mode data_mode, bool write) { struct cmd_desc *desc; desc = allocate_descriptor(); desc->direction = write; desc->multi_io_mode = data_mode; desc->data_address = (uint32_t)(uintptr_t) data; desc->length = data_bytes; if (write) dcache_clean_by_mva(data, data_bytes); else dcache_invalidate_by_mva(data, data_bytes); } static void queue_data(uint8_t *data, uint32_t data_bytes, enum qspi_mode data_mode, bool write) { uint8_t *aligned_ptr; uint8_t *epilog_ptr; uint32_t prolog_bytes, aligned_bytes, epilog_bytes; if (data_bytes == 0) return; aligned_ptr = (uint8_t *)ALIGN_UP((uintptr_t)data, CACHE_LINE_SIZE); prolog_bytes = MIN(data_bytes, aligned_ptr - data); aligned_bytes = ALIGN_DOWN(data_bytes - prolog_bytes, CACHE_LINE_SIZE); epilog_bytes = data_bytes - prolog_bytes - aligned_bytes; epilog_ptr = data + prolog_bytes + aligned_bytes; if (prolog_bytes) queue_bounce_data(data, prolog_bytes, data_mode, write); if (aligned_bytes) queue_direct_data(aligned_ptr, aligned_bytes, data_mode, write); if (epilog_bytes) queue_bounce_data(epilog_ptr, epilog_bytes, data_mode, write); } static void reg_init(void) { uint32_t spi_mode; uint32_t tx_data_oe_delay, tx_data_delay; uint32_t mstr_config; spi_mode = 0; tx_data_oe_delay = 0; tx_data_delay = 0; mstr_config = (tx_data_oe_delay << TX_DATA_OE_DELAY_SHIFT) | (tx_data_delay << TX_DATA_DELAY_SHIFT) | (SBL_EN) | (spi_mode << SPI_MODE_SHIFT) | (PIN_HOLDN) | (FB_CLK_EN) | (DMA_ENABLE) | (FULL_CYCLE_MODE); write32(&qcom_qspi->mstr_cfg, mstr_config); write32(&qcom_qspi->ahb_mstr_cfg, 0xA42); write32(&qcom_qspi->mstr_int_en, 0x0); write32(&qcom_qspi->mstr_int_sts, 0xFFFFFFFF); write32(&qcom_qspi->rd_fifo_cfg, 0x0); write32(&qcom_qspi->rd_fifo_rst, RESET_FIFO); } void quadspi_init(uint32_t hz) { assert(dcache_line_bytes() == CACHE_LINE_SIZE); clock_configure_qspi(hz * 4); configure_gpios(); reg_init(); } int qspi_claim_bus(const struct spi_slave *slave) { cs_change(CS_ASSERT); return 0; } void qspi_release_bus(const struct spi_slave *slave) { cs_change(CS_DEASSERT); } static int xfer(enum qspi_mode mode, const void *dout, size_t out_bytes, void *din, size_t in_bytes) { if ((out_bytes && !dout) || (in_bytes && !din) || (in_bytes && out_bytes)) { return -1; } queue_data((uint8_t *) (out_bytes ? dout : din), in_bytes | out_bytes, mode, !!out_bytes); flush_chain(); return 0; } int qspi_xfer(const struct spi_slave *slave, const void *dout, size_t out_bytes, void *din, size_t in_bytes) { return xfer(SDR_1BIT, dout, out_bytes, din, in_bytes); } int qspi_xfer_dual(const struct spi_slave *slave, const void *dout, size_t out_bytes, void *din, size_t in_bytes) { return xfer(SDR_2BIT, dout, out_bytes, din, in_bytes); }