304 lines
9.4 KiB
C
304 lines
9.4 KiB
C
/* Copyright 2018 Canaan Inc.
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <devices.h>
|
|
#include "w25qxx.h"
|
|
#include "printf.h"
|
|
#include "iomem.h"
|
|
|
|
uintptr_t spi_adapter;
|
|
uintptr_t spi_stand;
|
|
static SemaphoreHandle_t event;
|
|
|
|
|
|
static enum w25qxx_status_t w25qxx_receive_data(uint8_t* cmd_buff, uint8_t cmd_len, uint8_t* rx_buff, uint32_t rx_len)
|
|
{
|
|
//xSemaphoreTake(event, portMAX_DELAY);
|
|
|
|
spi_dev_transfer_sequential(spi_stand, (uint8_t *)cmd_buff, cmd_len, (uint8_t *)rx_buff, rx_len);
|
|
//xSemaphoreGive(event);
|
|
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_receive_data_enhanced(uint32_t* cmd_buff, uint8_t cmd_len, uint8_t* rx_buff, uint32_t rx_len)
|
|
{
|
|
//xSemaphoreTake(event, portMAX_DELAY);
|
|
memcpy(rx_buff, cmd_buff, cmd_len);
|
|
io_read(spi_adapter, (uint8_t *)rx_buff, rx_len);
|
|
//xSemaphoreGive(event);
|
|
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_send_data(uintptr_t file, uint8_t* cmd_buff, uint8_t cmd_len, uint8_t* tx_buff, uint32_t tx_len)
|
|
{
|
|
configASSERT(cmd_len);
|
|
//xSemaphoreTake(event, portMAX_DELAY);
|
|
uint8_t* tmp_buf = iomem_malloc(cmd_len + tx_len);
|
|
memcpy(tmp_buf, cmd_buff, cmd_len);
|
|
if (tx_len)
|
|
memcpy(tmp_buf + cmd_len, tx_buff, tx_len);
|
|
io_write(file, (uint8_t *)tmp_buf, cmd_len + tx_len);
|
|
iomem_free(tmp_buf);
|
|
//xSemaphoreGive(event);
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_write_enable(void)
|
|
{
|
|
uint8_t cmd[1] = {WRITE_ENABLE};
|
|
|
|
w25qxx_send_data(spi_stand, cmd, 1, 0, 0);
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_read_status_reg1(uint8_t* reg_data)
|
|
{
|
|
uint8_t cmd[1] = {READ_REG1};
|
|
uint8_t data[1];
|
|
|
|
w25qxx_receive_data(cmd, 1, data, 1);
|
|
*reg_data = data[0];
|
|
return W25QXX_OK;
|
|
}
|
|
static enum w25qxx_status_t w25qxx_read_status_reg2(uint8_t* reg_data)
|
|
{
|
|
uint8_t cmd[1] = {READ_REG2};
|
|
uint8_t data[1];
|
|
|
|
w25qxx_receive_data(cmd, 1, data, 1);
|
|
*reg_data = data[0];
|
|
return W25QXX_OK;
|
|
}
|
|
static enum w25qxx_status_t w25qxx_write_status_reg(uint8_t reg1_data, uint8_t reg2_data)
|
|
{
|
|
uint8_t cmd[3] = {WRITE_REG1, reg1_data, reg2_data};
|
|
|
|
w25qxx_write_enable();
|
|
w25qxx_send_data(spi_stand, cmd, 3, 0, 0);
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_enable_quad_mode(void)
|
|
{
|
|
uint8_t reg_data;
|
|
|
|
w25qxx_read_status_reg2(®_data);
|
|
if (!(reg_data & REG2_QUAL_MASK))
|
|
{
|
|
reg_data |= REG2_QUAL_MASK;
|
|
w25qxx_write_status_reg(0x00, reg_data);
|
|
}
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_is_busy(void)
|
|
{
|
|
uint8_t status;
|
|
|
|
w25qxx_read_status_reg1(&status);
|
|
if (status & REG1_BUSY_MASK)
|
|
return W25QXX_BUSY;
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
enum w25qxx_status_t w25qxx_sector_erase(uint32_t addr)
|
|
{
|
|
uint8_t cmd[4] = {SECTOR_ERASE};
|
|
|
|
cmd[1] = (uint8_t)(addr >> 16);
|
|
cmd[2] = (uint8_t)(addr >> 8);
|
|
cmd[3] = (uint8_t)(addr);
|
|
w25qxx_write_enable();
|
|
w25qxx_send_data(spi_stand, cmd, 4, 0, 0);
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
enum w25qxx_status_t w25qxx_read_id(uint8_t *manuf_id, uint8_t *device_id)
|
|
{
|
|
uint8_t cmd[4] = {READ_ID, 0x00, 0x00, 0x00};
|
|
uint8_t data[2] = {0};
|
|
|
|
w25qxx_receive_data(cmd, 4, data, 2);
|
|
*manuf_id = data[0];
|
|
*device_id = data[1];
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_read_data_less_64kb(uint32_t addr, uint8_t* data_buf, uint32_t length)
|
|
{
|
|
uint32_t cmd[2];
|
|
|
|
switch (WORK_TRANS_MODE)
|
|
{
|
|
case SPI_FF_DUAL:
|
|
*(((uint8_t*)cmd) + 0) = FAST_READ_DUAL_OUTPUT;
|
|
*(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0);
|
|
*(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
|
|
*(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16);
|
|
w25qxx_receive_data_enhanced(cmd, 4, data_buf, length);
|
|
break;
|
|
case SPI_FF_QUAD:
|
|
*(((uint8_t*)cmd) + 0) = FAST_READ_QUAL_OUTPUT;
|
|
*(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0);
|
|
*(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
|
|
*(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16);
|
|
w25qxx_receive_data_enhanced(cmd, 4, data_buf, length);
|
|
break;
|
|
case SPI_FF_STANDARD:
|
|
default:
|
|
*(((uint8_t*)cmd) + 0) = READ_DATA;
|
|
*(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 16);
|
|
*(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
|
|
*(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 0);
|
|
w25qxx_receive_data((uint8_t*)cmd, 4, data_buf, length);
|
|
break;
|
|
}
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
enum w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t* data_buf, uint32_t length)
|
|
{
|
|
uint32_t len;
|
|
|
|
while (length)
|
|
{
|
|
len = length >= 0x010000 ? 0x010000 : length;
|
|
w25qxx_read_data_less_64kb(addr, data_buf, len);
|
|
addr += len;
|
|
data_buf += len;
|
|
length -= len;
|
|
}
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_page_program(uint32_t addr, uint8_t* data_buf, uint32_t length)
|
|
{
|
|
uint32_t cmd[2];
|
|
w25qxx_write_enable();
|
|
if (WORK_TRANS_MODE == SPI_FF_QUAD)
|
|
{
|
|
*(((uint8_t*)cmd) + 0) = QUAD_PAGE_PROGRAM;
|
|
*(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0);
|
|
*(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
|
|
*(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16);
|
|
w25qxx_send_data(spi_adapter, (uint8_t*)cmd, 4, data_buf, length);
|
|
}
|
|
else
|
|
{
|
|
*(((uint8_t*)cmd) + 0) = PAGE_PROGRAM;
|
|
*(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 16);
|
|
*(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8);
|
|
*(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 0);
|
|
w25qxx_send_data(spi_stand, (uint8_t*)cmd, 4, data_buf, length);
|
|
}
|
|
while (w25qxx_is_busy() == W25QXX_BUSY)
|
|
;
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
static enum w25qxx_status_t w25qxx_sector_program(uint32_t addr, uint8_t* data_buf)
|
|
{
|
|
uint8_t index;
|
|
|
|
for (index = 0; index < w25qxx_FLASH_PAGE_NUM_PER_SECTOR; index++)
|
|
{
|
|
w25qxx_page_program(addr, data_buf, w25qxx_FLASH_PAGE_SIZE);
|
|
addr += w25qxx_FLASH_PAGE_SIZE;
|
|
data_buf += w25qxx_FLASH_PAGE_SIZE;
|
|
}
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
enum w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t* data_buf, uint32_t length)
|
|
{
|
|
uint32_t sector_addr, sector_offset, sector_remain, write_len, index;
|
|
uint8_t *swap_buf = (uint8_t *)iomem_malloc(w25qxx_FLASH_SECTOR_SIZE);
|
|
uint8_t *pread, *pwrite;
|
|
|
|
while (length)
|
|
{
|
|
sector_addr = addr & (~(w25qxx_FLASH_SECTOR_SIZE - 1));
|
|
sector_offset = addr & (w25qxx_FLASH_SECTOR_SIZE - 1);
|
|
sector_remain = w25qxx_FLASH_SECTOR_SIZE - sector_offset;
|
|
write_len = length < sector_remain ? length : sector_remain;
|
|
w25qxx_read_data(sector_addr, swap_buf, w25qxx_FLASH_SECTOR_SIZE);
|
|
pread = swap_buf + sector_offset;
|
|
pwrite = data_buf;
|
|
for (index = 0; index < write_len; index++)
|
|
{
|
|
if ((*pwrite) != ((*pwrite) & (*pread)))
|
|
{
|
|
w25qxx_sector_erase(sector_addr);
|
|
while (w25qxx_is_busy() == W25QXX_BUSY)
|
|
;
|
|
break;
|
|
}
|
|
pwrite++;
|
|
pread++;
|
|
}
|
|
if (write_len == w25qxx_FLASH_SECTOR_SIZE)
|
|
w25qxx_sector_program(sector_addr, data_buf);
|
|
else
|
|
{
|
|
pread = swap_buf + sector_offset;
|
|
pwrite = data_buf;
|
|
for (index = 0; index < write_len; index++)
|
|
*pread++ = *pwrite++;
|
|
w25qxx_sector_program(sector_addr, swap_buf);
|
|
}
|
|
length -= write_len;
|
|
addr += write_len;
|
|
data_buf += write_len;
|
|
}
|
|
iomem_free(swap_buf);
|
|
return W25QXX_OK;
|
|
}
|
|
|
|
enum w25qxx_status_t w25qxx_init(uintptr_t spi_in)
|
|
{
|
|
uint8_t manuf_id, device_id;
|
|
event = xSemaphoreCreateMutex();
|
|
spi_stand = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_STANDARD, CHIP_SELECT, FRAME_LENGTH);
|
|
spi_dev_set_clock_rate(spi_stand, 25000000);
|
|
w25qxx_read_id(&manuf_id, &device_id);
|
|
if ((manuf_id != 0xEF && manuf_id != 0xC8) || (device_id != 0x17 && device_id != 0x16))
|
|
{
|
|
printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id);
|
|
}
|
|
printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id);
|
|
switch (WORK_TRANS_MODE)
|
|
{
|
|
case SPI_FF_DUAL:
|
|
spi_adapter = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_DUAL, CHIP_SELECT, FRAME_LENGTH);
|
|
spi_dev_config_non_standard(spi_adapter, INSTRUCTION_LENGTH, ADDRESS_LENGTH, WAIT_CYCLE, SPI_AITM_STANDARD);
|
|
break;
|
|
case SPI_FF_QUAD:
|
|
spi_adapter = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_QUAD, CHIP_SELECT, FRAME_LENGTH);
|
|
spi_dev_config_non_standard(spi_adapter, INSTRUCTION_LENGTH, ADDRESS_LENGTH, WAIT_CYCLE, SPI_AITM_STANDARD);
|
|
w25qxx_enable_quad_mode();
|
|
break;
|
|
case SPI_FF_STANDARD:
|
|
default:
|
|
spi_adapter = spi_stand;
|
|
break;
|
|
}
|
|
return W25QXX_OK;
|
|
}
|
|
|