Newer
Older
mbed-os / targets / TARGET_ARM_SSG / TARGET_MUSCA_S1 / device / drivers / qspi_ip6514e_drv.c
@Gabor Toth Gabor Toth on 10 Sep 2020 28 KB Add platform support to Musca S1
/*
 * Copyright (c) 2018-2019 Arm Limited
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
/* Use memcpy */
#include <string.h>

#include "qspi_ip6514e_drv.h"

/** Setter bit manipulation macro */
#define SET_BIT(WORD, BIT_INDEX) ((WORD) |= (1U << (BIT_INDEX)))
/** Clearing bit manipulation macro */
#define CLR_BIT(WORD, BIT_INDEX) ((WORD) &= ~(1U << (BIT_INDEX)))
/** Getter bit manipulation macro */
#define GET_BIT(WORD, BIT_INDEX) (bool)(((WORD) & (1U << (BIT_INDEX))))

#define WORD_ALIGN_4B_MASK    0x3U /* Mask the first 2 bits */
#define IS_ADDR_ALIGNED(ADDR) (((uint32_t)(ADDR) & (WORD_ALIGN_4B_MASK)) == 0U)

#define BITS_PER_BYTE               8U
#define BITS_PER_WORD               32U

#define CFG_READS  true
#define CFG_WRITES false

#define ARG_NOT_USED     0
#define ARG_PTR_NOT_USED NULL

#define DATA_REG_NUMBER 2U
#define DATA_REG_LOWER  0U
#define DATA_REG_UPPER  1U

#define ERROR_VALUE 0xFFFFFFFFU

/**
 * \brief QSPI IP6514E register map structure
 */
struct _qspi_ip6514e_reg_map_t {
    volatile uint32_t qspi_cfg;                           /*!< 0x00 (R/W) */
    volatile uint32_t device_read_inst;                   /*!< 0x04 (R/W) */
    volatile uint32_t device_write_inst;                  /*!< 0x08 (R/W) */
    volatile uint32_t hidden1[2];
    volatile uint32_t device_size;                        /*!< 0x14 (R/W) */
    volatile uint32_t hidden2[3];
    volatile uint32_t remap_addr;                         /*!< 0x24 (R/W) */
    volatile uint32_t hidden3[26];
    volatile uint32_t flash_cmd_ctrl;                     /*!< 0x90 (R/W) */
    volatile uint32_t flash_cmd_addr;                     /*!< 0x94 (R/W) */
    volatile uint32_t hidden4[2];
    volatile uint32_t flash_cmd_read_data_lower;          /*!< 0xA0 (R/ ) */
    volatile uint32_t flash_cmd_read_data_upper;          /*!< 0xA4 (R/ ) */
    volatile uint32_t flash_cmd_write_data_lower;         /*!< 0xA8 (R/W) */
    volatile uint32_t flash_cmd_write_data_upper;         /*!< 0xAC (R/W) */
    volatile uint32_t hidden5[2];
};

/** QSPI Configuration register description (offset 0x00) */
#define QSPI_CFG_ENABLE_POS            0U
#define QSPI_CFG_ENABLE_ADDR_REMAP_POS 16U
#define QSPI_CFG_BAUD_DIV_POS          19U
    #define QSPI_CFG_BAUD_DIV_MIN          2U
    #define QSPI_CFG_BAUD_DIV_MAX          32U
    #define QSPI_CFG_BAUD_DIV_BITS         4U
#define QSPI_CFG_IDLE_POS              31U

/**
 * Device Read/Write Instruction registers description (offset 0x04 and 0x08).
 * These values are the same for the Device Read Instruction register at offset
 * 0x04 and the Device Write Instruction register at offset 0x08.
 */
#define DEVICE_READ_WRITE_INST_OPCODE_POS        0U
#define DEVICE_READ_INST_INST_TYPE_POS           8U /* Only applies to the Read
                                                     * register. */
#define DEVICE_READ_WRITE_INST_ADDR_TYPE_POS     12U
#define DEVICE_READ_WRITE_INST_DATA_TYPE_POS     16U
    #define DEVICE_READ_WRITE_INST_MODE_QSPI         2U
    #define DEVICE_READ_WRITE_INST_MODE_DSPI         1U
    #define DEVICE_READ_WRITE_INST_MODE_SPI          0U
    #define DEVICE_READ_WRITE_INST_MODE_BITS         2U
#define DEVICE_READ_WRITE_INST_DUMMY_CYCLES_POS  24U
    #define DEVICE_READ_WRITE_INST_DUMMY_CYCLES_BITS 5U
    #define DEVICE_READ_WRITE_INST_DUMMY_CYCLES_MAX  31U

/** Device Size Configuration register description (offset 0x14) */
#define DEVICE_SIZE_ADDR_BYTES_POS  0U
    #define DEVICE_SIZE_ADDR_BYTES_MIN  1U
    #define DEVICE_SIZE_ADDR_BYTES_MAX  16U
    #define DEVICE_SIZE_ADDR_BYTES_BITS 4U
#define DEVICE_SIZE_PAGE_BYTES_POS  4U
    #define DEVICE_SIZE_PAGE_BYTES_MAX  4095U
    #define DEVICE_SIZE_PAGE_BYTES_BITS 12U

/** Flash Command Control register description (offset 0x90) */
#define FLASH_CMD_CTRL_EXECUTE_POS        0U
#define FLASH_CMD_CTRL_BUSY_POS           1U
#define FLASH_CMD_CTRL_DUMMY_CYCLES_POS   7U
    #define FLASH_CMD_CTRL_DUMMY_CYCLES_MAX   31U
    #define FLASH_CMD_CTRL_DUMMY_CYCLES_BITS  5U
#define FLASH_CMD_CTRL_WRITE_BYTES_POS    12U
    #define FLASH_CMD_CTRL_WRITE_BYTES_MAX    8U
    #define FLASH_CMD_CTRL_WRITE_BYTES_BITS   3U
#define FLASH_CMD_CTRL_WRITE_ENABLE_POS   15U
#define FLASH_CMD_CTRL_ADDR_BYTES_POS     16U
    #define FLASH_CMD_CTRL_ADDR_BYTES_MAX     4U
    #define FLASH_CMD_CTRL_ADDR_BYTES_BITS    2U
#define FLASH_CMD_CTRL_ADDR_ENABLE_POS    19U
#define FLASH_CMD_CTRL_READ_BYTES_POS     20U
    #define FLASH_CMD_CTRL_READ_BYTES_MAX     8U
    #define FLASH_CMD_CTRL_READ_BYTES_BITS    3U
#define FLASH_CMD_CTRL_READ_ENABLE_POS    23U
#define FLASH_CMD_CTRL_OPCODE_POS         24U

/** Default register values of the QSPI Flash controller */
#define QSPI_CFG_REG_RESET_VALUE              (0x80080080U)
#define DEVICE_READ_INSTR_REG_RESET_VALUE     (0x080220EBU)
#define DEVICE_WRITE_INSTR_REG_RESET_VALUE    (0x00000002U)
#define DEVICE_SIZE_CFG_REG_RESET_VALUE       (0x00101002U)
#define REMAP_ADDR_REG_RESET_VALUE            (0x00000000U)
#define FLASH_CMD_CONTROL_REG_RESET_VALUE     (0x00000000U)
#define FLASH_CMD_ADDRESS_REG_RESET_VALUE     (0x00000000U)
#define FLASH_CMD_WRITE_DATA_REG_RESET_VALUE  (0x00000000U)

/**
 * \brief Change specific bits in a 32 bits word.
 *
 * \param[in,out] word         Pointer of the word to change
 * \param[in]     bits         bits_length bits to put at bits_pos in the word
 *                             pointed
 * \param[in]     bits_length  Number of bits to change
 * \param[in]     bits_pos     Position of the bits to change
 *
 * \note This function will do nothing if the parameters given are incorret:
 *         * word is NULL
 *         * bits_length + bits_pos > 32
 *         * bits_length is 0
 */
static void change_bits_in_word(volatile uint32_t *word,
                                uint32_t bits,
                                uint32_t bits_length,
                                uint32_t bits_pos)
{
    uint32_t mask;

    if ((word == NULL) ||
        ((bits_length + bits_pos) > BITS_PER_WORD) ||
        (bits_length == 0U)) {
        /* Silently fail */
        return;
    }

    /* Change all the bits */
    if (bits_length == BITS_PER_WORD) {
        *word = bits;
        return;
    }

    mask = ((1U << bits_length) - 1);
    /*
     * We change the bits in three steps:
     *   - clear bits_length bits with zeroes at bits_pos in the word
     *   - mask bits in case it contains more than bits_length bits
     *   - set the new bits in the cleared word
     * Because the data pointed by word is only read once, the data will still
     * be coherent after an interruption that changes it.
     */
    *word = ((*word & ~(mask << bits_pos)) | ((bits & mask) << bits_pos));
}

/**
 * \brief Configure reads or writes commands for direct operations.
 *
 * \param[in] dev             QSPI IP6514E device struct \ref qspi_ip6514e_dev_t
 * \param[in] opcode          Read/write opcode that will be used for every
 *                            direct read/write
 * \param[in] dummy_cycles    Number of dummy cycles to wait before triggering
 *                            the command, this value must be between 0 and 31
 *                            (both included)
 * \param[in] is_reads_cfg    true to configure direct reads, false to configure
 *                            direct writes
 *
 * \return Returns error code as specified in \ref qspi_ip6514e_error_t
 *
 * \note The QSPI controller should be idle before calling this function.
 */
static enum qspi_ip6514e_error_t qspi_ip6514e_cfg_reads_writes(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint8_t opcode,
                                                 uint32_t dummy_cycles,
                                                 bool is_reads_cfg)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;
    /*
     * Select the good register address if we want to configure reads or writes.
     */
    volatile uint32_t *device_read_write_inst_reg = is_reads_cfg ?
                                                  &(reg_map->device_read_inst) :
                                                  &(reg_map->device_write_inst);
    uint32_t device_read_write_inst_reg_copy = *device_read_write_inst_reg;

    /*
     * Wait for the Serial Interface and QSPI pipeline to be IDLE when
     * all low level synchronization has been done.
     */
    while(!qspi_ip6514e_is_idle(dev));

    if (dummy_cycles > DEVICE_READ_WRITE_INST_DUMMY_CYCLES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    change_bits_in_word(&device_read_write_inst_reg_copy,
                        (uint32_t)opcode,
                        BITS_PER_BYTE,
                        DEVICE_READ_WRITE_INST_OPCODE_POS);
    change_bits_in_word(&device_read_write_inst_reg_copy,
                        dummy_cycles,
                        DEVICE_READ_WRITE_INST_DUMMY_CYCLES_BITS,
                        DEVICE_READ_WRITE_INST_DUMMY_CYCLES_POS);

    *device_read_write_inst_reg = device_read_write_inst_reg_copy;

    return QSPI_IP6514E_ERR_NONE;
}

/**
 * \brief Given the public SPI mode enumeration, returns the private value it
 *        maps to in the register field.
 *
 * \param[in] spi_mode Read/write opcode that will be used for every direct
 *            read/write
 *
 * \return Return the correct DEVICE_READ_WRITE_INST_MODE value.
 */
static uint32_t spi_mode_field_value(enum qspi_ip6514e_spi_mode_t spi_mode)
{
    switch (spi_mode) {
        case QSPI_IP6514E_SPI_MODE:
            return DEVICE_READ_WRITE_INST_MODE_SPI;
        case QSPI_IP6514E_DSPI_MODE:
            return DEVICE_READ_WRITE_INST_MODE_DSPI;
        case QSPI_IP6514E_QSPI_MODE:
            return DEVICE_READ_WRITE_INST_MODE_QSPI;
        default:
            return ERROR_VALUE;
    }
}

bool qspi_ip6514e_is_idle(struct qspi_ip6514e_dev_t* dev)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    return GET_BIT(reg_map->qspi_cfg, QSPI_CFG_IDLE_POS);
}

bool qspi_ip6514e_is_enabled(struct qspi_ip6514e_dev_t* dev)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    return GET_BIT(reg_map->qspi_cfg, QSPI_CFG_ENABLE_POS);
}

void qspi_ip6514e_disable(struct qspi_ip6514e_dev_t* dev)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    CLR_BIT(reg_map->qspi_cfg, QSPI_CFG_ENABLE_POS);
}

void qspi_ip6514e_enable(struct qspi_ip6514e_dev_t* dev)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    SET_BIT(reg_map->qspi_cfg, QSPI_CFG_ENABLE_POS);
}

enum qspi_ip6514e_error_t qspi_ip6514e_set_baud_rate_div(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint32_t div)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    /*
     * Wait for the Serial Interface and QSPI pipeline to be IDLE when
     * all low level synchronization has been done.
     */
    while(!qspi_ip6514e_is_idle(dev));

    /* div should be an even number. */
    if (((div & 1U) == 1) ||
        (div < QSPI_CFG_BAUD_DIV_MIN) ||
        (div > QSPI_CFG_BAUD_DIV_MAX)) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    /*
     * The div value (between 2 and 32) needs to be stored in the register on a
     * 4 bits field.
     */
    change_bits_in_word(&(reg_map->qspi_cfg),
                        (div / 2) - 1,
                        QSPI_CFG_BAUD_DIV_BITS,
                        QSPI_CFG_BAUD_DIV_POS);

    return QSPI_IP6514E_ERR_NONE;
}

enum qspi_ip6514e_error_t qspi_ip6514e_set_spi_mode(
                                         struct qspi_ip6514e_dev_t* dev,
                                         enum qspi_ip6514e_spi_mode_t inst_type,
                                         enum qspi_ip6514e_spi_mode_t addr_type,
                                         enum qspi_ip6514e_spi_mode_t data_type)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;
    uint32_t inst_spi_mode, addr_spi_mode, data_spi_mode;
    /*
     * A local copy of the Device Read Instruction and Device Write Instruction
     * registers is used to limit APB accesses.
     */
    uint32_t device_read_inst_cpy = reg_map->device_read_inst;
    uint32_t device_write_inst_cpy = reg_map->device_write_inst;

    /*
     * Wait for the Serial Interface and QSPI pipeline to be IDLE when
     * all low level synchronization has been done.
     */
    while(!qspi_ip6514e_is_idle(dev));

    /*
     * First check that the instruction mode is not SPI. If that is the case,
     * the address and data mode register fields become DO NOT CARE.
     */
    inst_spi_mode = spi_mode_field_value(inst_type);
    if (inst_spi_mode == ERROR_VALUE) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }
    if (inst_type != QSPI_IP6514E_SPI_MODE) {
        change_bits_in_word(&(reg_map->device_read_inst),
                            inst_spi_mode,
                            DEVICE_READ_WRITE_INST_MODE_BITS,
                            DEVICE_READ_INST_INST_TYPE_POS);
        return QSPI_IP6514E_ERR_NONE;
    }

    /* Now check and set address and data modes. */
    addr_spi_mode = spi_mode_field_value(addr_type);
    data_spi_mode = spi_mode_field_value(data_type);
    if ((addr_spi_mode == ERROR_VALUE) || (data_spi_mode == ERROR_VALUE)) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    /* Change the Device Read Instruction register. */
    change_bits_in_word(&device_read_inst_cpy,
                        inst_spi_mode,
                        DEVICE_READ_WRITE_INST_MODE_BITS,
                        DEVICE_READ_INST_INST_TYPE_POS);
    change_bits_in_word(&device_read_inst_cpy,
                        addr_spi_mode,
                        DEVICE_READ_WRITE_INST_MODE_BITS,
                        DEVICE_READ_WRITE_INST_ADDR_TYPE_POS);
    change_bits_in_word(&device_read_inst_cpy,
                        data_spi_mode,
                        DEVICE_READ_WRITE_INST_MODE_BITS,
                        DEVICE_READ_WRITE_INST_DATA_TYPE_POS);

    /* Change the Device Write Instruction register. */
    change_bits_in_word(&device_write_inst_cpy,
                        addr_spi_mode,
                        DEVICE_READ_WRITE_INST_MODE_BITS,
                        DEVICE_READ_WRITE_INST_ADDR_TYPE_POS);
    change_bits_in_word(&device_write_inst_cpy,
                        data_spi_mode,
                        DEVICE_READ_WRITE_INST_MODE_BITS,
                        DEVICE_READ_WRITE_INST_DATA_TYPE_POS);

    /* Save the changes. */
    reg_map->device_read_inst = device_read_inst_cpy;
    reg_map->device_write_inst = device_write_inst_cpy;

    return QSPI_IP6514E_ERR_NONE;
}

enum qspi_ip6514e_error_t qspi_ip6514e_cfg_reads(struct qspi_ip6514e_dev_t* dev,
                                                 uint8_t opcode,
                                                 uint32_t dummy_cycles)
{
    return qspi_ip6514e_cfg_reads_writes(dev, opcode, dummy_cycles, CFG_READS);
}

enum qspi_ip6514e_error_t qspi_ip6514e_cfg_writes(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint8_t opcode,
                                                 uint32_t dummy_cycles)
{
    return qspi_ip6514e_cfg_reads_writes(dev, opcode, dummy_cycles, CFG_WRITES);
}

enum qspi_ip6514e_error_t qspi_ip6514e_cfg_page_size(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint32_t page_size)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    /*
     * Wait for the Serial Interface and QSPI pipeline to be IDLE when
     * all low level synchronization has been done.
     */
    while(!qspi_ip6514e_is_idle(dev));

    if (page_size > DEVICE_SIZE_PAGE_BYTES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    change_bits_in_word(&(reg_map->device_size),
                        page_size,
                        DEVICE_SIZE_PAGE_BYTES_BITS,
                        DEVICE_SIZE_PAGE_BYTES_POS);

    return QSPI_IP6514E_ERR_NONE;
}

enum qspi_ip6514e_error_t qspi_ip6514e_cfg_addr_bytes(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint32_t bytes_number)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    /*
     * Wait for the Serial Interface and QSPI pipeline to be IDLE when
     * all low level synchronization has been done.
     */
    while(!qspi_ip6514e_is_idle(dev));

    if (bytes_number < DEVICE_SIZE_ADDR_BYTES_MIN ||
        bytes_number > DEVICE_SIZE_ADDR_BYTES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    change_bits_in_word(&(reg_map->device_size),
                        bytes_number - 1,
                        DEVICE_SIZE_ADDR_BYTES_BITS,
                        DEVICE_SIZE_ADDR_BYTES_POS);


    return QSPI_IP6514E_ERR_NONE;
}

void qspi_ip6514e_remap_addr(struct qspi_ip6514e_dev_t* dev, uint32_t offset)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;
    /* Save the enable state to restore it after. */
    bool is_enabled = qspi_ip6514e_is_enabled(dev);

    if (is_enabled) {
        qspi_ip6514e_disable(dev);
    }

    reg_map->remap_addr = offset;
    SET_BIT(reg_map->qspi_cfg, QSPI_CFG_ENABLE_ADDR_REMAP_POS);

    if (is_enabled) {
        qspi_ip6514e_enable(dev);
    }
}

void qspi_ip6514e_disable_remap(struct qspi_ip6514e_dev_t* dev)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;
    /* Save the enable state to restore it after. */
    bool is_enabled = qspi_ip6514e_is_enabled(dev);

    if (is_enabled) {
        qspi_ip6514e_disable(dev);
    }

    CLR_BIT(reg_map->qspi_cfg, QSPI_CFG_ENABLE_ADDR_REMAP_POS);

    if (is_enabled) {
        qspi_ip6514e_enable(dev);
    }
}

void qspi_ip6514e_reset_regs(struct qspi_ip6514e_dev_t* dev)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;

    /* Restore the default value of the QSPI Configuration register. */
    reg_map->qspi_cfg = QSPI_CFG_REG_RESET_VALUE;

    /* Restore the default value of the Device R/W Instruction registers. */
    reg_map->device_read_inst = DEVICE_READ_INSTR_REG_RESET_VALUE;
    reg_map->device_write_inst = DEVICE_WRITE_INSTR_REG_RESET_VALUE;

    /* Restore the default value of the Device Size Configuration register. */
    reg_map->device_size = DEVICE_SIZE_CFG_REG_RESET_VALUE;

    /* Restore the default value of the Remap Address register. */
    reg_map->remap_addr = REMAP_ADDR_REG_RESET_VALUE;

    /* Restore the default value of the Flash Command Control register. */
    reg_map->flash_cmd_ctrl = FLASH_CMD_CONTROL_REG_RESET_VALUE;
    /* Restore the default value of the Flash Command Address register. */
    reg_map->flash_cmd_addr = FLASH_CMD_ADDRESS_REG_RESET_VALUE;

    /* Restore the default value of the Flash Command Write Data registers. */
    reg_map->flash_cmd_write_data_lower = FLASH_CMD_WRITE_DATA_REG_RESET_VALUE;
    reg_map->flash_cmd_write_data_upper = FLASH_CMD_WRITE_DATA_REG_RESET_VALUE;

    /*
     * This function does not affect the Flash Command Read Data registers
     * which are completely Read-Only.
     */
}

enum qspi_ip6514e_error_t qspi_ip6514e_send_cmd(struct qspi_ip6514e_dev_t* dev,
                                                uint8_t opcode,
                                                void *read_data,
                                                uint32_t read_len,
                                                const void *write_data,
                                                uint32_t write_len,
                                                uint32_t addr,
                                                uint32_t addr_bytes_number,
                                                uint32_t dummy_cycles)
{
    struct _qspi_ip6514e_reg_map_t *reg_map =
                               (struct _qspi_ip6514e_reg_map_t *)dev->cfg->base;
    /* To limit APB accesses, we set this reg up locally before */
    uint32_t flash_cmd_ctrl = 0U;
    bool read_requested = ((read_data != NULL) && (read_len != 0));
    bool write_requested = ((write_data != NULL) && (write_len != 0));
    bool addr_requested = (addr_bytes_number != 0);
    /*
     * To prevent unaligned and byte or halfbyte accesses to the APB registers,
     * a word aligned buffer is used to temporary transfer the data before doing
     * word accesses on these registers from that buffer.
     */
    uint32_t data_regs[DATA_REG_NUMBER] = {0};

    if (read_len > FLASH_CMD_CTRL_READ_BYTES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    if (write_len > FLASH_CMD_CTRL_WRITE_BYTES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    if (addr_bytes_number > FLASH_CMD_CTRL_ADDR_BYTES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    if (dummy_cycles > FLASH_CMD_CTRL_DUMMY_CYCLES_MAX) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    if (read_requested && write_requested) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    change_bits_in_word(&flash_cmd_ctrl,
                        (uint32_t)opcode,
                        BITS_PER_BYTE,
                        FLASH_CMD_CTRL_OPCODE_POS);

    /* Enable read if requested */
    if (read_requested) {
        SET_BIT(flash_cmd_ctrl, FLASH_CMD_CTRL_READ_ENABLE_POS);
        change_bits_in_word(&flash_cmd_ctrl,
                            read_len - 1,
                            FLASH_CMD_CTRL_READ_BYTES_BITS,
                            FLASH_CMD_CTRL_READ_BYTES_POS);
    }

    /* Enable write if requested */
    if (write_requested) {
        SET_BIT(flash_cmd_ctrl, FLASH_CMD_CTRL_WRITE_ENABLE_POS);
        change_bits_in_word(&flash_cmd_ctrl,
                            write_len - 1,
                            FLASH_CMD_CTRL_WRITE_BYTES_BITS,
                            FLASH_CMD_CTRL_WRITE_BYTES_POS);

        if (IS_ADDR_ALIGNED(write_data) && IS_ADDR_ALIGNED(write_len)) {
            /*
             * Optimised case when write_data is word aligned and write_len is
             * 4 or 8.
             */
            reg_map->flash_cmd_write_data_lower = *(uint32_t *)write_data;
            if (write_len == FLASH_CMD_CTRL_WRITE_BYTES_MAX) {
                reg_map->flash_cmd_write_data_upper =
                                                  *((uint32_t *)write_data + 1);
            }
        } else {
            /*
             * data_regs is used as a buffer to only do unaligned access on the
             * AHB bus and word aligned accesses to the APB registers.
             */
            memcpy((void *)data_regs, write_data, write_len);
            /*
             * Only write_len bytes will be written even if both data registers
             * are written.
             */
            reg_map->flash_cmd_write_data_lower = data_regs[DATA_REG_LOWER];
            reg_map->flash_cmd_write_data_upper = data_regs[DATA_REG_UPPER];
        }
    }

    /* Enable the address if requested */
    if (addr_requested) {
        SET_BIT(flash_cmd_ctrl, FLASH_CMD_CTRL_ADDR_ENABLE_POS);
        reg_map->flash_cmd_addr = addr;
        change_bits_in_word(&flash_cmd_ctrl,
                            addr_bytes_number - 1,
                            FLASH_CMD_CTRL_ADDR_BYTES_BITS,
                            FLASH_CMD_CTRL_ADDR_BYTES_POS);
    }

    /* Put dummy cycles number */
    change_bits_in_word(&flash_cmd_ctrl,
                        dummy_cycles,
                        FLASH_CMD_CTRL_DUMMY_CYCLES_BITS,
                        FLASH_CMD_CTRL_DUMMY_CYCLES_POS);

    /* Copy the Flash Command Control register and execute the command */
    reg_map->flash_cmd_ctrl = flash_cmd_ctrl;
    SET_BIT(reg_map->flash_cmd_ctrl, FLASH_CMD_CTRL_EXECUTE_POS);

    /* Wait for termination */
    while (GET_BIT(reg_map->flash_cmd_ctrl, FLASH_CMD_CTRL_BUSY_POS));

    /*
     * Recolt the read data if it was requested. read_len validity has already
     * been verified at this point.
     */
    if (read_requested) {
        if (IS_ADDR_ALIGNED(read_data) && IS_ADDR_ALIGNED(read_len)) {
            /*
             * Optimised case when read_data is word aligned and read_len is
             * 4 or 8.
             */
            *(uint32_t *)read_data = reg_map->flash_cmd_read_data_lower;
            if (read_len == FLASH_CMD_CTRL_READ_BYTES_MAX) {
                *((uint32_t *)read_data + 1) =
                                             reg_map->flash_cmd_read_data_upper;
            }
        } else {
            /*
             * Only read_len bytes have been written even if both data registers
             * are written.
             */
            data_regs[DATA_REG_LOWER] = reg_map->flash_cmd_read_data_lower;
            data_regs[DATA_REG_UPPER] = reg_map->flash_cmd_read_data_upper;
            /*
             * data_regs is used as a buffer to only do unaligned access on the
             * AHB bus and word aligned accesses to the APB registers.
             */
            memcpy(read_data, (void *)data_regs, read_len);
        }
    }

    return QSPI_IP6514E_ERR_NONE;
}

void qspi_ip6514e_send_simple_cmd(struct qspi_ip6514e_dev_t* dev,
                                      uint8_t opcode)
{
    /*
     * No read/write data, no address, no dummy cycles.
     * Given the arguments, this function can not fail.
     */
    (void)qspi_ip6514e_send_cmd(dev,
                                opcode,
                                ARG_PTR_NOT_USED,
                                ARG_NOT_USED,
                                ARG_PTR_NOT_USED,
                                ARG_NOT_USED,
                                ARG_NOT_USED,
                                ARG_NOT_USED,
                                0);
}

enum qspi_ip6514e_error_t qspi_ip6514e_send_read_cmd(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint8_t opcode,
                                                 void *read_data,
                                                 uint32_t read_len,
                                                 uint32_t addr,
                                                 uint32_t addr_bytes_number,
                                                 uint32_t dummy_cycles)
{
    /* Read arguments are expected */
    if (read_data == ARG_PTR_NOT_USED || read_len == ARG_NOT_USED) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    /* No write data */
    return qspi_ip6514e_send_cmd(dev,
                                 opcode,
                                 read_data,
                                 read_len,
                                 ARG_PTR_NOT_USED,
                                 ARG_NOT_USED,
                                 addr,
                                 addr_bytes_number,
                                 dummy_cycles);
}

enum qspi_ip6514e_error_t qspi_ip6514e_send_write_cmd(
                                                 struct qspi_ip6514e_dev_t* dev,
                                                 uint8_t opcode,
                                                 const void *write_data,
                                                 uint32_t write_len,
                                                 uint32_t addr,
                                                 uint32_t addr_bytes_number,
                                                 uint32_t dummy_cycles)
{
    /* Write arguments are expected */
    if (write_data == ARG_PTR_NOT_USED || write_len == ARG_NOT_USED) {
        return QSPI_IP6514E_ERR_WRONG_ARGUMENT;
    }

    /* No read data, no dummy cycles */
    return qspi_ip6514e_send_cmd(dev,
                                 opcode,
                                 ARG_PTR_NOT_USED,
                                 ARG_NOT_USED,
                                 write_data,
                                 write_len,
                                 addr,
                                 addr_bytes_number,
                                 dummy_cycles);
}