/**************************************************************************//** * * @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2021-2-11 Wayne First version * ******************************************************************************/ #include #if defined(BSP_USING_QSPI) #include #include "NuMicro.h" #include #include #include #define LOG_TAG "drv.qspi" #define DBG_ENABLE #define DBG_SECTION_NAME LOG_TAG #define DBG_LEVEL DBG_INFO #define DBG_COLOR #include #include #include #include /* Private define ---------------------------------------------------------------*/ /* fsclk = fpclk / ((div+1)*2), but div=1 is suggested. */ #define DEF_SPI_MAX_SPEED (SPI_INPUT_CLOCK/((1)*2)) enum { QSPI_START = -1, #if defined(BSP_USING_QSPI0) QSPI0_IDX, #endif #if defined(BSP_USING_QSPI1) QSPI1_IDX, #endif QSPI_CNT }; /* Private typedef --------------------------------------------------------------*/ struct nu_qspi { struct rt_spi_bus dev; char *name; uint32_t idx; E_SYS_IPRST rstidx; E_SYS_IPCLK clkidx; uint32_t dummy; struct rt_qspi_configuration configuration; }; typedef struct nu_qspi *nu_qspi_t; /* Private functions ------------------------------------------------------------*/ static void nu_qspi_transmission_with_poll(struct nu_qspi *spi_bus, uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word); static int nu_qspi_register_bus(struct nu_qspi *spi_bus, const char *name); static rt_uint32_t nu_qspi_bus_xfer(struct rt_spi_device *device, struct rt_spi_message *message); static rt_err_t nu_qspi_bus_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration); /* Public functions -------------------------------------------------------------*/ /* Private variables ------------------------------------------------------------*/ static struct rt_spi_ops nu_qspi_poll_ops = { .configure = nu_qspi_bus_configure, .xfer = nu_qspi_bus_xfer, }; static struct nu_qspi nu_qspi_arr [] = { #if defined(BSP_USING_QSPI0) { .name = "qspi0", .idx = 0, .rstidx = SPI0RST, .clkidx = SPI0CKEN, }, #endif #if defined(BSP_USING_QSPI1) { .name = "qspi1", .idx = 1, .rstidx = SPI1RST, .clkidx = SPI1CKEN, }, #endif }; /* nu_qspi */ static rt_err_t nu_qspi_bus_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration) { struct nu_qspi *qspi_bus; uint32_t u32SPIMode; uint32_t u32SPISpeed; rt_err_t ret = RT_EOK; RT_ASSERT(device != RT_NULL); RT_ASSERT(configuration != RT_NULL); qspi_bus = (struct nu_qspi *) device->bus; /* Check mode */ switch (configuration->mode & RT_SPI_MODE_3) { case RT_SPI_MODE_0: u32SPIMode = SPI_MODE_0; break; case RT_SPI_MODE_1: u32SPIMode = SPI_MODE_1; break; case RT_SPI_MODE_2: u32SPIMode = SPI_MODE_2; break; case RT_SPI_MODE_3: u32SPIMode = SPI_MODE_3; break; default: ret = RT_EIO; goto exit_nu_qspi_bus_configure; } /* Check data width */ if (!(configuration->data_width == 8 || configuration->data_width == 16 || configuration->data_width == 24 || configuration->data_width == 32)) { ret = RT_EINVAL; goto exit_nu_qspi_bus_configure; } /* Need to initialize new configuration? */ if (rt_memcmp(configuration, &qspi_bus->configuration, sizeof(*configuration)) != 0) { rt_memcpy(&qspi_bus->configuration, configuration, sizeof(*configuration)); /* Set mode */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_MODE, (uint32_t)u32SPIMode, 0); /* Set data width */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_TX_BITLEN, (uint32_t)configuration->data_width, 0); /* Set speed */ u32SPISpeed = configuration->max_hz; if (u32SPISpeed > DEF_SPI_MAX_SPEED) u32SPISpeed = DEF_SPI_MAX_SPEED; u32SPISpeed = spiIoctl(qspi_bus->idx, SPI_IOC_SET_SPEED, u32SPISpeed, 0); LOG_I("Actual=%dHz, Prefer=%dHz", u32SPISpeed, configuration->max_hz); /* Disable auto-select */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_AUTOSS, SPI_DISABLE_AUTOSS, 0); if (configuration->mode & RT_SPI_CS_HIGH) { /* Set CS pin to LOW */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_SS_ACTIVE_LEVEL, SPI_SS_ACTIVE_HIGH, 0); } else { /* Set CS pin to HIGH */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_SS_ACTIVE_LEVEL, SPI_SS_ACTIVE_LOW, 0); } if (configuration->mode & RT_SPI_MSB) { /* Set sequence to MSB first */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_LSB_MSB, SPI_MSB, 0); } else { /* Set sequence to LSB first */ spiIoctl(qspi_bus->idx, SPI_IOC_SET_LSB_MSB, SPI_LSB, 0); } } exit_nu_qspi_bus_configure: return -(ret); } static int nu_qspi_read(uint32_t idx, uint32_t buf_id, uint8_t *recv_addr, uint8_t bytes_per_word) { uint32_t val; // Read data from SPI RX FIFO switch (bytes_per_word) { case 4: val = spiRead(idx, buf_id); nu_set32_le(recv_addr, val); break; case 3: val = spiRead(idx, buf_id); nu_set24_le(recv_addr, val); break; case 2: val = spiRead(idx, buf_id); nu_set16_le(recv_addr, val); break; case 1: *recv_addr = spiRead(idx, buf_id); break; default: LOG_E("Data length is not supported.\n"); return 0; } return bytes_per_word; } static int nu_qspi_write(uint32_t idx, uint32_t buf_id, const uint8_t *send_addr, uint8_t bytes_per_word) { // Input data to SPI TX switch (bytes_per_word) { case 4: spiWrite(idx, buf_id, nu_get32_le(send_addr)); break; case 3: spiWrite(idx, buf_id, nu_get24_le(send_addr)); break; case 2: spiWrite(idx, buf_id, nu_get16_le(send_addr)); break; case 1: spiWrite(idx, buf_id, *((uint8_t *)send_addr)); break; default: LOG_E("Data length is not supported.\n"); return 0; } return bytes_per_word; } /** * @brief SPI bus polling * @param dev : The pointer of the specified SPI module. * @param send_addr : Source address * @param recv_addr : Destination address * @param length : Data length */ static void nu_qspi_transmission_with_poll(struct nu_qspi *spi_bus, uint8_t *send_addr, uint8_t *recv_addr, int length, uint8_t bytes_per_word) { uint32_t idx = spi_bus->idx; int trans_num = length / bytes_per_word; while (trans_num > 0) { int i; uint32_t u32TxNum = (trans_num > 4) ? 4 : trans_num; for (i = 0; i < u32TxNum; i++) { /* Write TX data into TX-buffer */ if ((send_addr != RT_NULL)) { send_addr += nu_qspi_write(idx, i, (const uint8_t *)send_addr, bytes_per_word); } else /* read-only */ { spi_bus->dummy = 0; nu_qspi_write(idx, i, (const uint8_t *)&spi_bus->dummy, bytes_per_word); } } /* Set TX transacation number */ spiIoctl(idx, SPI_IOC_SET_TX_NUM, u32TxNum - 1, 0); /* Trigger SPI communication. */ spiIoctl(idx, SPI_IOC_TRIGGER, 0, 0); /* Wait it done. */ while (spiGetBusyStatus(idx)) {}; /* Read data from RX-buffer */ if ((recv_addr != RT_NULL)) { for (i = 0; i < u32TxNum; i++) { recv_addr += nu_qspi_read(idx, i, recv_addr, bytes_per_word); } } trans_num -= u32TxNum; } } void nu_qspi_transfer(struct nu_qspi *spi_bus, uint8_t *tx, uint8_t *rx, int length, uint8_t bytes_per_word) { RT_ASSERT(spi_bus != RT_NULL); nu_qspi_transmission_with_poll(spi_bus, tx, rx, length, bytes_per_word); } static int nu_qspi_mode_config(struct nu_qspi *spi_bus, rt_uint8_t *tx, rt_uint8_t *rx, int qspi_lines) { uint32_t idx = spi_bus->idx; if (qspi_lines > 1) { if (tx) { switch (qspi_lines) { case 2: spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_MODE, SPI_DUAL_MODE, 0); break; case 4: spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_MODE, SPI_QUAD_MODE, 0); break; default: LOG_E("Data line is not supported.\n"); return -1; } spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_DIR, SPI_DUAL_QUAD_OUTPUT, 0); } else if (rx) { switch (qspi_lines) { case 2: spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_MODE, SPI_DUAL_MODE, 0); break; case 4: spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_MODE, SPI_QUAD_MODE, 0); break; default: LOG_E("Data line is not supported.\n"); return -1; } spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_DIR, SPI_DUAL_QUAD_INPUT, 0); } } else { spiIoctl(idx, SPI_IOC_SET_DUAL_QUAD_MODE, SPI_DISABLE_DUAL_QUAD, 0); } return qspi_lines; } static rt_uint32_t nu_qspi_bus_xfer(struct rt_spi_device *device, struct rt_spi_message *message) { struct nu_qspi *spi_bus; struct rt_qspi_configuration *qspi_configuration; struct rt_qspi_message *qspi_message; rt_uint8_t u8last = 1; rt_uint8_t bytes_per_word; uint32_t idx; rt_uint32_t u32len = 0; RT_ASSERT(device != RT_NULL); RT_ASSERT(message != RT_NULL); spi_bus = (struct nu_qspi *) device->bus; idx = spi_bus->idx; qspi_configuration = &spi_bus->configuration; bytes_per_word = qspi_configuration->parent.data_width / 8; if (message->cs_take && !(qspi_configuration->parent.mode & RT_SPI_NO_CS)) { /* /CS: active */ /* We just use CS0 only. if you need CS1, please use pin controlling before sending message. */ spiIoctl(idx, SPI_IOC_ENABLE_SS, SPI_SS_SS0, 0); } qspi_message = (struct rt_qspi_message *)message; /* Command + Address + Dummy + Data */ /* Command stage */ if (qspi_message->instruction.content != 0) { u8last = nu_qspi_mode_config(spi_bus, (rt_uint8_t *) &qspi_message->instruction.content, RT_NULL, qspi_message->instruction.qspi_lines); nu_qspi_transfer((struct nu_qspi *)spi_bus, (rt_uint8_t *) &qspi_message->instruction.content, RT_NULL, 1, 1); } /* Address stage */ if (qspi_message->address.size > 0) { rt_uint32_t u32ReversedAddr = 0; rt_uint32_t u32AddrNumOfByte = qspi_message->address.size / 8; switch (u32AddrNumOfByte) { case 1: u32ReversedAddr = (qspi_message->address.content & 0xff); break; case 2: nu_set16_be((rt_uint8_t *)&u32ReversedAddr, qspi_message->address.content); break; case 3: nu_set24_be((rt_uint8_t *)&u32ReversedAddr, qspi_message->address.content); break; case 4: nu_set32_be((rt_uint8_t *)&u32ReversedAddr, qspi_message->address.content); break; default: RT_ASSERT(0); break; } u8last = nu_qspi_mode_config(spi_bus, (rt_uint8_t *)&u32ReversedAddr, RT_NULL, qspi_message->address.qspi_lines); nu_qspi_transfer((struct nu_qspi *)spi_bus, (rt_uint8_t *) &u32ReversedAddr, RT_NULL, u32AddrNumOfByte, 1); } /* alternate_bytes stage */ if ((qspi_message->alternate_bytes.size > 0) && (qspi_message->alternate_bytes.size <= 4)) { rt_uint32_t u32AlternateByte = 0; rt_uint32_t u32NumOfByte = qspi_message->alternate_bytes.size / 8; switch (u32NumOfByte) { case 1: u32AlternateByte = (qspi_message->alternate_bytes.content & 0xff); break; case 2: nu_set16_be((rt_uint8_t *)&u32AlternateByte, qspi_message->alternate_bytes.content); break; case 3: nu_set24_be((rt_uint8_t *)&u32AlternateByte, qspi_message->alternate_bytes.content); break; case 4: nu_set32_be((rt_uint8_t *)&u32AlternateByte, qspi_message->alternate_bytes.content); break; default: RT_ASSERT(0); break; } u8last = nu_qspi_mode_config(spi_bus, (rt_uint8_t *)&u32AlternateByte, RT_NULL, qspi_message->alternate_bytes.qspi_lines); nu_qspi_transfer((struct nu_qspi *)spi_bus, (rt_uint8_t *) &u32AlternateByte, RT_NULL, u32NumOfByte, 1); } /* Dummy_cycles stage */ if (qspi_message->dummy_cycles > 0) { spi_bus->dummy = 0x00; u8last = nu_qspi_mode_config(spi_bus, (rt_uint8_t *) &spi_bus->dummy, RT_NULL, u8last); nu_qspi_transfer((struct nu_qspi *)spi_bus, (rt_uint8_t *) &spi_bus->dummy, RT_NULL, qspi_message->dummy_cycles / (8 / u8last), 1); } if (message->length > 0) { /* Data stage */ nu_qspi_mode_config(spi_bus, (rt_uint8_t *) message->send_buf, (rt_uint8_t *) message->recv_buf, qspi_message->qspi_data_lines); nu_qspi_transfer((struct nu_qspi *)spi_bus, (rt_uint8_t *) message->send_buf, (rt_uint8_t *) message->recv_buf, message->length, bytes_per_word); u32len = message->length; } else { u32len = 1; } if (message->cs_release && !(qspi_configuration->parent.mode & RT_SPI_NO_CS)) { /* /CS: deactive */ /* We just use CS0 only. if you need CS1, please use pin controlling before sending message. */ spiIoctl(idx, SPI_IOC_DISABLE_SS, SPI_SS_SS0, 0); } return u32len; } static int nu_qspi_register_bus(struct nu_qspi *spi_bus, const char *name) { return rt_qspi_bus_register(&spi_bus->dev, name, &nu_qspi_poll_ops); } /** * Hardware SPI Initial */ static int rt_hw_qspi_init(void) { int i; for (i = (QSPI_START + 1); i < QSPI_CNT; i++) { nu_sys_ipclk_enable(nu_qspi_arr[i].clkidx); nu_sys_ip_reset(nu_qspi_arr[i].rstidx); spiOpen(nu_qspi_arr[i].idx); nu_qspi_register_bus(&nu_qspi_arr[i], nu_qspi_arr[i].name); } return 0; } INIT_DEVICE_EXPORT(rt_hw_qspi_init); rt_err_t nu_qspi_bus_attach_device(const char *bus_name, const char *device_name, rt_uint8_t data_line_width, void (*enter_qspi_mode)(), void (*exit_qspi_mode)()) { struct rt_qspi_device *qspi_device = RT_NULL; rt_err_t result = RT_EOK; RT_ASSERT(bus_name != RT_NULL); RT_ASSERT(device_name != RT_NULL); RT_ASSERT(data_line_width == 1 || data_line_width == 2 || data_line_width == 4); qspi_device = (struct rt_qspi_device *)rt_malloc(sizeof(struct rt_qspi_device)); if (qspi_device == RT_NULL) { LOG_E("no memory, qspi bus attach device failed!\n"); result = -RT_ENOMEM; goto __exit; } qspi_device->enter_qspi_mode = enter_qspi_mode; qspi_device->exit_qspi_mode = exit_qspi_mode; qspi_device->config.qspi_dl_width = data_line_width; result = rt_spi_bus_attach_device(&qspi_device->parent, device_name, bus_name, RT_NULL); __exit: if (result != RT_EOK) { if (qspi_device) { rt_free(qspi_device); } } return result; } #endif //#if defined(BSP_USING_SPI)