/* * Copyright (c) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ /* * This driver is written based on the Altera's * Nios-II QSPI Controller HAL driver. */ #include #include #include #include #include #include #include #include #include #include "flash_priv.h" #include "altera_generic_quad_spi_controller2_regs.h" #include "altera_generic_quad_spi_controller2.h" /* * Remove the following macros once the Altera HAL * supports the QSPI Controller v2 IP. */ #define ALTERA_QSPI_CONTROLLER2_FLAG_STATUS_REG 0x0000001C #define FLAG_STATUS_PROTECTION_ERROR (1 << 1) #define FLAG_STATUS_PROGRAM_SUSPENDED (1 << 2) #define FLAG_STATUS_PROGRAM_ERROR (1 << 4) #define FLAG_STATUS_ERASE_ERROR (1 << 5) #define FLAG_STATUS_ERASE_SUSPENDED (1 << 6) #define FLAG_STATUS_CONTROLLER_READY (1 << 7) /* ALTERA_QSPI_CONTROLLER2_STATUS_REG bits */ #define STATUS_PROTECTION_POS 2 #define STATUS_PROTECTION_MASK 0x1F #define STATUS_PROTECTION_EN_VAL 0x17 #define STATUS_PROTECTION_DIS_VAL 0x0 /* ALTERA_QSPI_CONTROLLER2_MEM_OP_REG bits */ #define MEM_OP_ERASE_CMD 0x00000002 #define MEM_OP_WRITE_EN_CMD 0x00000004 #define MEM_OP_SECTOR_OFFSET_BIT_POS 8 #define MEM_OP_UNLOCK_ALL_SECTORS 0x00000003 #define MEM_OP_LOCK_ALL_SECTORS 0x00000F03 #define NIOS2_QSPI_BLANK_WORD 0xFFFFFFFF #define NIOS2_WRITE_BLOCK_SIZE 4 #define USEC_TO_MSEC(x) (x / 1000) struct flash_nios2_qspi_config { alt_qspi_controller2_dev qspi_dev; struct k_sem sem_lock; }; static int flash_nios2_qspi_erase(struct device *dev, off_t offset, size_t len) { struct flash_nios2_qspi_config *flash_cfg = dev->driver_data; alt_qspi_controller2_dev *qspi_dev = &flash_cfg->qspi_dev; u32_t block_offset, offset_in_block, length_to_erase; u32_t erase_offset = offset; /* address of next byte to erase */ u32_t remaining_length = len; /* length of data left to be erased */ u32_t flag_status; s32_t rc = 0, i, timeout; k_sem_take(&flash_cfg->sem_lock, K_FOREVER); /* * check if offset is word aligned and * length is with in the range */ if (((offset + len) > qspi_dev->data_end) || (0 != (erase_offset & (NIOS2_WRITE_BLOCK_SIZE - 1)))) { SYS_LOG_ERR("erase failed at offset %ld\n", offset); rc = -EINVAL; goto qspi_erase_err; } for (i = offset/qspi_dev->sector_size; i < qspi_dev->number_of_sectors; i++) { if ((remaining_length <= 0) || erase_offset >= (offset + len)) { break; } block_offset = 0; /* block offset in byte addressing */ offset_in_block = 0; /* offset into current sector to erase */ length_to_erase = 0; /* length to erase in current sector */ /* calculate current sector/block offset in byte addressing */ block_offset = erase_offset & ~(qspi_dev->sector_size - 1); /* calculate offset into sector/block if there is one */ if (block_offset != erase_offset) { offset_in_block = erase_offset - block_offset; } /* calculate the byte size of data to be written in a sector */ length_to_erase = min(qspi_dev->sector_size - offset_in_block, remaining_length); /* Erase sector */ IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_MEM_OP_REG, MEM_OP_WRITE_EN_CMD); IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_MEM_OP_REG, (i << MEM_OP_SECTOR_OFFSET_BIT_POS) | MEM_OP_ERASE_CMD); /* * poll the status register to know the * completion of the erase operation. */ timeout = ALTERA_QSPI_CONTROLLER2_1US_TIMEOUT_VALUE; while (timeout > 0) { /* wait for 1 usec */ k_busy_wait(1); flag_status = IORD_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_FLAG_STATUS_REG); if (flag_status & FLAG_STATUS_CONTROLLER_READY) { break; } timeout--; } if ((flag_status & FLAG_STATUS_ERASE_ERROR) || (flag_status & FLAG_STATUS_PROTECTION_ERROR)) { SYS_LOG_ERR("erase failed, Flag Status Reg:%x\n", flag_status); rc = -EIO; goto qspi_erase_err; } /* update remaining length and erase_offset */ remaining_length -= length_to_erase; erase_offset += length_to_erase; } qspi_erase_err: k_sem_give(&flash_cfg->sem_lock); return rc; } static int flash_nios2_qspi_write_block(struct device *dev, int block_offset, int mem_offset, const void *data, size_t len) { struct flash_nios2_qspi_config *flash_cfg = dev->driver_data; alt_qspi_controller2_dev *qspi_dev = &flash_cfg->qspi_dev; u32_t buffer_offset = 0; /* offset into data buffer to get write data */ u32_t remaining_length = len; /* length left to write */ u32_t write_offset = mem_offset; /* offset into flash to write too */ u32_t word_to_write, padding, bytes_to_copy; u32_t flag_status; s32_t rc = 0; while (remaining_length > 0) { /* initialize word to write to blank word */ word_to_write = NIOS2_QSPI_BLANK_WORD; /* bytes to pad the next word that is written */ padding = 0; /* number of bytes from source to copy */ bytes_to_copy = NIOS2_WRITE_BLOCK_SIZE; /* * we need to make sure the write is word aligned * this should only be true at most 1 time */ if (0 != (write_offset & (NIOS2_WRITE_BLOCK_SIZE - 1))) { /* * data is not word aligned calculate padding bytes * need to add before start of a data offset */ padding = write_offset & (NIOS2_WRITE_BLOCK_SIZE - 1); /* * update variables to account * for padding being added */ bytes_to_copy -= padding; if (bytes_to_copy > remaining_length) { bytes_to_copy = remaining_length; } write_offset = write_offset - padding; if (0 != (write_offset & (NIOS2_WRITE_BLOCK_SIZE - 1))) { rc = -EINVAL; goto qspi_write_block_err; } } else { if (bytes_to_copy > remaining_length) { bytes_to_copy = remaining_length; } } /* Check memcpy lentgh is with in NIOS2_WRITE_BLOCK_SIZE */ if (padding + bytes_to_copy > NIOS2_WRITE_BLOCK_SIZE) { rc = -EINVAL; goto qspi_write_block_err; } /* prepare the word to be written */ memcpy((u8_t *)&word_to_write + padding, data + buffer_offset, bytes_to_copy); /* enable write */ IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_MEM_OP_REG, MEM_OP_WRITE_EN_CMD); /* write to flash 32 bits at a time */ IOWR_32DIRECT(qspi_dev->data_base, write_offset, word_to_write); /* check whether write operation is successful */ flag_status = IORD_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_FLAG_STATUS_REG); if ((flag_status & FLAG_STATUS_PROGRAM_ERROR) || (flag_status & FLAG_STATUS_PROTECTION_ERROR)) { SYS_LOG_ERR("write failed, Flag Status Reg:%x\n", flag_status); rc = -EIO; /* sector might be protected */ goto qspi_write_block_err; } /* update offset and length variables */ buffer_offset += bytes_to_copy; remaining_length -= bytes_to_copy; write_offset = write_offset + NIOS2_WRITE_BLOCK_SIZE; } qspi_write_block_err: return rc; } static int flash_nios2_qspi_write(struct device *dev, off_t offset, const void *data, size_t len) { struct flash_nios2_qspi_config *flash_cfg = dev->driver_data; alt_qspi_controller2_dev *qspi_dev = &flash_cfg->qspi_dev; u32_t block_offset, offset_in_block, length_to_write; u32_t write_offset = offset; /* address of next byte to write */ u32_t buffer_offset = 0; /* offset into source buffer */ u32_t remaining_length = len; /* length of data left to be written */ s32_t rc = 0, i; k_sem_take(&flash_cfg->sem_lock, K_FOREVER); /* * check if offset is word aligned and * length is with in the range */ if ((data == NULL) || ((offset + len) > qspi_dev->data_end) || (0 != (write_offset & (NIOS2_WRITE_BLOCK_SIZE - 1)))) { SYS_LOG_ERR("write failed at offset %ld\n", offset); rc = -EINVAL; goto qspi_write_err; } for (i = offset/qspi_dev->sector_size; i < qspi_dev->number_of_sectors; i++) { if (remaining_length <= 0) { break; } block_offset = 0; /* block offset in byte addressing */ offset_in_block = 0; /* offset into current sector to write */ length_to_write = 0; /* length to write to current sector */ /* calculate current sector/block offset in byte addressing */ block_offset = write_offset & ~(qspi_dev->sector_size - 1); /* calculate offset into sector/block if there is one */ if (block_offset != write_offset) { offset_in_block = write_offset - block_offset; } /* calculate the byte size of data to be written in a sector */ length_to_write = min(qspi_dev->sector_size - offset_in_block, remaining_length); rc = flash_nios2_qspi_write_block(dev, block_offset, write_offset, data + buffer_offset, length_to_write); if (rc < 0) { goto qspi_write_err; } /* update remaining length and buffer_offset */ remaining_length -= length_to_write; buffer_offset += length_to_write; write_offset += length_to_write; } qspi_write_err: k_sem_give(&flash_cfg->sem_lock); return rc; } static int flash_nios2_qspi_read(struct device *dev, off_t offset, void *data, size_t len) { struct flash_nios2_qspi_config *flash_cfg = dev->driver_data; alt_qspi_controller2_dev *qspi_dev = &flash_cfg->qspi_dev; u32_t buffer_offset = 0; /* offset into data buffer to get read data */ u32_t remaining_length = len; /* length left to read */ u32_t read_offset = offset; /* offset into flash to read from */ u32_t word_to_read, bytes_to_copy; s32_t rc = 0; k_sem_take(&flash_cfg->sem_lock, K_FOREVER); /* * check if offset is word aligned and * length is with in the range */ if ((data == NULL) || ((offset + len) > qspi_dev->data_end) || (0 != (read_offset & (NIOS2_WRITE_BLOCK_SIZE - 1)))) { SYS_LOG_ERR("read failed at offset %ld\n", offset); rc = -EINVAL; goto qspi_read_err; } while (remaining_length > 0) { /* number of bytes from source to copy */ bytes_to_copy = NIOS2_WRITE_BLOCK_SIZE; if (bytes_to_copy > remaining_length) { bytes_to_copy = remaining_length; } /* read from flash 32 bits at a time */ word_to_read = IORD_32DIRECT(qspi_dev->data_base, read_offset); memcpy(data + buffer_offset, &word_to_read, bytes_to_copy); /* update offset and length variables */ read_offset += bytes_to_copy; buffer_offset += bytes_to_copy; remaining_length -= bytes_to_copy; } qspi_read_err: k_sem_give(&flash_cfg->sem_lock); return rc; } static int flash_nios2_qspi_write_protection(struct device *dev, bool enable) { struct flash_nios2_qspi_config *flash_cfg = dev->driver_data; alt_qspi_controller2_dev *qspi_dev = &flash_cfg->qspi_dev; u32_t status, lock_val; s32_t rc = 0, timeout; k_sem_take(&flash_cfg->sem_lock, K_FOREVER); /* set write enable */ IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_MEM_OP_REG, MEM_OP_WRITE_EN_CMD); if (enable) { IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_MEM_OP_REG, MEM_OP_LOCK_ALL_SECTORS); lock_val = STATUS_PROTECTION_EN_VAL; } else { IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_MEM_OP_REG, MEM_OP_UNLOCK_ALL_SECTORS); lock_val = STATUS_PROTECTION_DIS_VAL; } /* * poll the status register to know the * completion of the erase operation. */ timeout = ALTERA_QSPI_CONTROLLER2_1US_TIMEOUT_VALUE; while (timeout > 0) { /* wait for 1 usec */ k_busy_wait(1); /* * read flash flag status register before * checking the QSPI status */ IORD_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_FLAG_STATUS_REG); /* read QPSI status register */ status = IORD_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_STATUS_REG); if (((status >> STATUS_PROTECTION_POS) & STATUS_PROTECTION_MASK) == lock_val) { break; } timeout--; } if (timeout <= 0) { SYS_LOG_ERR("locking failed, status-reg 0x%x\n", status); rc = -EIO; } /* clear flag status register */ IOWR_32DIRECT(qspi_dev->csr_base, ALTERA_QSPI_CONTROLLER2_FLAG_STATUS_REG, 0x0); k_sem_give(&flash_cfg->sem_lock); return rc; } static const struct flash_driver_api flash_nios2_qspi_api = { .write_protection = flash_nios2_qspi_write_protection, .erase = flash_nios2_qspi_erase, .write = flash_nios2_qspi_write, .read = flash_nios2_qspi_read, #if defined(CONFIG_FLASH_PAGE_LAYOUT) .page_layout = (flash_api_pages_layout) flash_page_layout_not_implemented, #endif .write_block_size = NIOS2_WRITE_BLOCK_SIZE, }; static int flash_nios2_qspi_init(struct device *dev) { struct flash_nios2_qspi_config *flash_cfg = dev->driver_data; k_sem_init(&flash_cfg->sem_lock, 1, 1); return 0; } struct flash_nios2_qspi_config flash_cfg = { .qspi_dev = { .data_base = EXT_FLASH_AVL_MEM_BASE, .data_end = EXT_FLASH_AVL_MEM_BASE + EXT_FLASH_AVL_MEM_SPAN, .csr_base = EXT_FLASH_AVL_CSR_BASE, .size_in_bytes = EXT_FLASH_AVL_MEM_SPAN, .is_epcs = EXT_FLASH_AVL_MEM_IS_EPCS, .number_of_sectors = EXT_FLASH_AVL_MEM_NUMBER_OF_SECTORS, .sector_size = EXT_FLASH_AVL_MEM_SECTOR_SIZE, .page_size = EXT_FLASH_AVL_MEM_PAGE_SIZE, } }; DEVICE_AND_API_INIT(flash_nios2_qspi, CONFIG_SOC_FLASH_NIOS2_QSPI_DEV_NAME, flash_nios2_qspi_init, &flash_cfg, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &flash_nios2_qspi_api);