| /* |
| * Copyright (c) 2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @brief File containing SPI device interface specific definitions for the |
| * Zephyr OS layer of the Wi-Fi driver. |
| */ |
| |
| #include <string.h> |
| |
| #include <zephyr/drivers/spi.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/drivers/wifi/nrf_wifi/bus/qspi_if.h> |
| |
| #include "spi_if.h" |
| |
| LOG_MODULE_DECLARE(wifi_nrf_bus, CONFIG_WIFI_NRF70_BUSLIB_LOG_LEVEL); |
| |
| #define NRF7002_NODE DT_NODELABEL(nrf70) |
| |
| static struct qspi_config *spim_config; |
| |
| static const struct spi_dt_spec spi_spec = |
| SPI_DT_SPEC_GET(NRF7002_NODE, SPI_WORD_SET(8) | SPI_TRANSFER_MSB); |
| |
| static struct spi_dt_spec spi_spec_8mhz = |
| SPI_DT_SPEC_GET(NRF7002_NODE, SPI_WORD_SET(8) | SPI_TRANSFER_MSB); |
| |
| static int spim_xfer_tx(unsigned int addr, void *data, unsigned int len) |
| { |
| int err; |
| uint8_t hdr[4] = { |
| 0x02, /* PP opcode */ |
| (((addr >> 16) & 0xFF) | 0x80), |
| (addr >> 8) & 0xFF, |
| (addr & 0xFF) |
| }; |
| |
| const struct spi_buf tx_buf[] = { |
| {.buf = hdr, .len = sizeof(hdr) }, |
| {.buf = data, .len = len }, |
| }; |
| const struct spi_buf_set tx = { .buffers = tx_buf, .count = 2 }; |
| |
| |
| err = spi_transceive_dt(&spi_spec, &tx, NULL); |
| |
| return err; |
| } |
| |
| |
| static int spim_xfer_rx(unsigned int addr, void *data, unsigned int len, unsigned int discard_bytes) |
| { |
| uint8_t hdr[] = { |
| 0x0b, /* FASTREAD opcode */ |
| (addr >> 16) & 0xFF, |
| (addr >> 8) & 0xFF, |
| addr & 0xFF, |
| 0 /* dummy byte */ |
| }; |
| uint8_t discard[sizeof(hdr) + 2 * 4]; |
| |
| const struct spi_buf tx_buf[] = { |
| {.buf = hdr, .len = sizeof(hdr) }, |
| {.buf = NULL, .len = len }, |
| }; |
| |
| const struct spi_buf_set tx = { .buffers = tx_buf, .count = 2 }; |
| |
| const struct spi_buf rx_buf[] = { |
| {.buf = discard, .len = sizeof(hdr) + discard_bytes}, |
| {.buf = data, .len = len }, |
| }; |
| |
| const struct spi_buf_set rx = { .buffers = rx_buf, .count = 2 }; |
| |
| if (rx_buf[0].len > sizeof(discard)) { |
| LOG_ERR("Discard bytes too large, please adjust buf size"); |
| return -EINVAL; |
| } |
| |
| return spi_transceive_dt(&spi_spec, &tx, &rx); |
| } |
| |
| int spim_read_reg(uint32_t reg_addr, uint8_t *reg_value) |
| { |
| int err; |
| uint8_t tx_buffer[6] = { reg_addr }; |
| |
| const struct spi_buf tx_buf = { |
| .buf = tx_buffer, |
| .len = sizeof(tx_buffer) |
| }; |
| |
| const struct spi_buf_set tx = { |
| .buffers = &tx_buf, |
| .count = 1 |
| }; |
| |
| uint8_t sr[6]; |
| |
| struct spi_buf rx_buf = { |
| .buf = &sr, |
| .len = sizeof(sr), |
| }; |
| const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 }; |
| |
| err = spi_transceive_dt(&spi_spec, &tx, &rx); |
| |
| LOG_DBG("err: %d -> %x %x %x %x %x %x", err, sr[0], sr[1], sr[2], sr[3], sr[4], sr[5]); |
| |
| if (err == 0) { |
| *reg_value = sr[1]; |
| } |
| |
| return err; |
| } |
| |
| int spim_write_reg(const struct spi_dt_spec *spi_spec, uint32_t reg_addr, const uint8_t reg_value) |
| { |
| int err; |
| uint8_t tx_buffer[] = { reg_addr, reg_value }; |
| |
| const struct spi_buf tx_buf = { .buf = tx_buffer, .len = sizeof(tx_buffer) }; |
| const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 }; |
| |
| err = spi_transceive_dt(spi_spec, &tx, NULL); |
| |
| if (err) { |
| LOG_ERR("SPI error: %d", err); |
| } |
| |
| return err; |
| } |
| |
| |
| int spim_RDSR1(const struct device *dev, uint8_t *rdsr1) |
| { |
| uint8_t val = 0; |
| |
| return spim_read_reg(0x1F, &val); |
| } |
| |
| int spim_RDSR2(const struct device *dev, uint8_t *rdsr1) |
| { |
| uint8_t val = 0; |
| |
| return spim_read_reg(0x2F, &val); |
| } |
| |
| int spim_WRSR2(const struct device *dev, const uint8_t wrsr2) |
| { |
| return spim_write_reg(&spi_spec, 0x3F, wrsr2); |
| } |
| |
| /** |
| * @brief Read a register via SPI (wrapper for compatibility) |
| * |
| * @param dev SPI device (unused, kept for compatibility) |
| * @param reg_addr Register address (opcode) |
| * @param reg_value Pointer to store the read value |
| * @return int 0 on success, negative error code on failure |
| */ |
| int spim_read_reg_wrapper(const struct device *dev, uint8_t reg_addr, uint8_t *reg_value) |
| { |
| ARG_UNUSED(dev); |
| return spim_read_reg(reg_addr, reg_value); |
| } |
| |
| /** |
| * @brief Write a register via SPI (wrapper for compatibility) |
| * |
| * @param dev SPI device (unused, kept for compatibility) |
| * @param reg_addr Register address (opcode) |
| * @param reg_value Value to write |
| * @return int 0 on success, negative error code on failure |
| */ |
| int spim_write_reg_wrapper(const struct device *dev, uint8_t reg_addr, uint8_t reg_value) |
| { |
| ARG_UNUSED(dev); |
| return spim_write_reg(&spi_spec, reg_addr, reg_value); |
| } |
| |
| int _spim_wait_while_rpu_awake(void) |
| { |
| int ret; |
| uint8_t val = 0; |
| |
| for (int ii = 0; ii < 10; ii++) { |
| |
| ret = spim_read_reg(0x1F, &val); |
| |
| LOG_DBG("RDSR1 = 0x%x", val); |
| |
| if (!ret && (val & RPU_AWAKE_BIT)) { |
| break; |
| } |
| |
| k_msleep(1); |
| } |
| |
| if (ret || !(val & RPU_AWAKE_BIT)) { |
| LOG_ERR("RPU is not awake even after 10ms"); |
| return -1; |
| } |
| |
| return val; |
| } |
| |
| /* Wait until RDSR2 confirms RPU_WAKEUP_NOW write is successful */ |
| int spim_wait_while_rpu_wake_write(void) |
| { |
| int ret; |
| uint8_t val = 0; |
| |
| for (int ii = 0; ii < 10; ii++) { |
| |
| ret = spim_read_reg(0x2F, &val); |
| |
| LOG_DBG("RDSR2 = 0x%x", val); |
| |
| if (!ret && (val & RPU_WAKEUP_NOW)) { |
| break; |
| } |
| |
| k_msleep(1); |
| } |
| |
| if (ret || !(val & RPU_WAKEUP_NOW)) { |
| LOG_ERR("RPU wakeup write ACK failed even after 10ms"); |
| return -1; |
| } |
| |
| return ret; |
| } |
| |
| int spim_cmd_rpu_wakeup(uint32_t data) |
| { |
| struct spi_dt_spec *spi_spec_tmp = (struct spi_dt_spec *)&spi_spec; |
| |
| if (spi_spec.config.frequency > MHZ(8)) { |
| spi_spec_tmp = &spi_spec_8mhz; |
| } |
| /* Waking RPU works reliably only with lowest frequency (8MHz) */ |
| return spim_write_reg(spi_spec_tmp, 0x3F, data); |
| } |
| |
| unsigned int spim_cmd_sleep_rpu(void) |
| { |
| int err; |
| uint8_t tx_buffer[] = { 0x3f, 0x0 }; |
| |
| const struct spi_buf tx_buf = { .buf = tx_buffer, .len = sizeof(tx_buffer) }; |
| const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 }; |
| |
| err = spi_transceive_dt(&spi_spec, &tx, NULL); |
| |
| if (err) { |
| LOG_ERR("SPI error: %d", err); |
| } |
| |
| return 0; |
| } |
| |
| int spim_init(struct qspi_config *config) |
| { |
| if (!spi_is_ready_dt(&spi_spec)) { |
| LOG_ERR("Device %s is not ready", spi_spec.bus->name); |
| return -ENODEV; |
| } |
| |
| spim_config = config; |
| |
| k_sem_init(&spim_config->lock, 1, 1); |
| |
| if (spi_spec.config.frequency >= MHZ(16)) { |
| spim_config->qspi_slave_latency = 1; |
| } |
| |
| spi_spec_8mhz.config.frequency = MHZ(8); |
| |
| LOG_INF("SPIM %s: freq = %d MHz", spi_spec.bus->name, |
| spi_spec.config.frequency / MHZ(1)); |
| LOG_INF("SPIM %s: latency = %d", spi_spec.bus->name, spim_config->qspi_slave_latency); |
| |
| return 0; |
| } |
| |
| int spim_deinit(void) |
| { |
| return spi_release_dt(&spi_spec); |
| } |
| |
| static void spim_addr_check(unsigned int addr, const void *data, unsigned int len) |
| { |
| if ((addr % 4 != 0) || (((unsigned int)data) % 4 != 0) || (len % 4 != 0)) { |
| LOG_ERR("%s : Unaligned address %x %x %d %x %x", __func__, addr, |
| (unsigned int)data, (addr % 4 != 0), (((unsigned int)data) % 4 != 0), |
| (len % 4 != 0)); |
| } |
| } |
| |
| int spim_write(unsigned int addr, const void *data, int len) |
| { |
| int status; |
| |
| spim_addr_check(addr, data, len); |
| |
| addr |= spim_config->addrmask; |
| |
| k_sem_take(&spim_config->lock, K_FOREVER); |
| |
| status = spim_xfer_tx(addr, (void *)data, len); |
| |
| k_sem_give(&spim_config->lock); |
| |
| return status; |
| } |
| |
| int spim_read(unsigned int addr, void *data, int len) |
| { |
| int status; |
| |
| spim_addr_check(addr, data, len); |
| |
| addr |= spim_config->addrmask; |
| |
| k_sem_take(&spim_config->lock, K_FOREVER); |
| |
| status = spim_xfer_rx(addr, data, len, 0); |
| |
| k_sem_give(&spim_config->lock); |
| |
| return status; |
| } |
| |
| static int spim_hl_readw(unsigned int addr, void *data) |
| { |
| int status = -1; |
| |
| k_sem_take(&spim_config->lock, K_FOREVER); |
| |
| status = spim_xfer_rx(addr, data, 4, 4 * spim_config->qspi_slave_latency); |
| |
| k_sem_give(&spim_config->lock); |
| |
| return status; |
| } |
| |
| int spim_hl_read(unsigned int addr, void *data, int len) |
| { |
| int count = 0; |
| |
| spim_addr_check(addr, data, len); |
| |
| while (count < (len / 4)) { |
| spim_hl_readw(addr + (4 * count), (char *)data + (4 * count)); |
| count++; |
| } |
| |
| return 0; |
| } |
| |
| /* ------------------------------added for wifi utils -------------------------------- */ |
| |
| int spim_cmd_rpu_wakeup_fn(uint32_t data) |
| { |
| return spim_cmd_rpu_wakeup(data); |
| } |
| |
| int spim_cmd_sleep_rpu_fn(void) |
| { |
| return spim_cmd_sleep_rpu(); |
| } |
| |
| int spim_wait_while_rpu_awake(void) |
| { |
| return _spim_wait_while_rpu_awake(); |
| } |
| |
| int spi_validate_rpu_wake_writecmd(void) |
| { |
| return spim_wait_while_rpu_wake_write(); |
| } |