You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
14 KiB
559 lines
14 KiB
/*
|
|
* Copyright (c) Hisilicon Technologies Co., Ltd. 2020-2020. All rights reserved.
|
|
* Description: spi module driver
|
|
* Author: Hisilicon
|
|
* Create: 2020-10-15
|
|
*/
|
|
|
|
#include "spi_raw.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
#include "soc_log.h"
|
|
#include "uapi_flash.h"
|
|
#include "nand.h"
|
|
#include "td_type.h"
|
|
#include "flash_ext.h"
|
|
#include "securec.h"
|
|
|
|
static struct nand_raw_ctrl *g_spiraw_ctl = NULL;
|
|
|
|
static td_s32 spi_raw_prope(struct mtd_partition *ptn, td_char *buf, td_s32 buf_len, td_s32 *readonly)
|
|
{
|
|
td_s32 dev = -1;
|
|
td_char tmp_buf[PATH_MAX] = {0};
|
|
|
|
if (buf_len <= 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (realpath(buf, tmp_buf) == NULL) {
|
|
soc_print("Failed to realpath: %s.\n", buf);
|
|
return -1;
|
|
}
|
|
|
|
dev = open(tmp_buf, O_RDWR);
|
|
if (dev == -1) {
|
|
dev = open(tmp_buf, O_RDONLY);
|
|
if (dev == -1) {
|
|
ptn->perm = ACCESS_NONE;
|
|
return -1;
|
|
}
|
|
ptn->perm = ACCESS_RD;
|
|
*readonly = 1;
|
|
} else {
|
|
ptn->perm = ACCESS_RDWR;
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
static td_s32 spi_raw_get_mtd_info(td_s32 dev, const struct mtd_info_user *mtdinfo)
|
|
{
|
|
if (ioctl(dev, MEMGETINFO, mtdinfo) != 0) {
|
|
soc_print("Can't get mtd information.\n");
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
if (mtdinfo->type != MTD_SPIFLASH) {
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
return TD_SUCCESS;
|
|
}
|
|
|
|
static uapi_flash_partinfo *spi_raw_get_part_info(td_s32 part)
|
|
{
|
|
uapi_flash_partinfo *part_info = NULL;
|
|
td_char devname[MAX_DEV_NAME_LEN]; /* 32 - buffer size */
|
|
|
|
memset_s(devname, sizeof(devname), 0, sizeof(devname));
|
|
if (snprintf_s(devname, sizeof(devname), sizeof(devname) - 1, "mtd%d", part) == -1) {
|
|
soc_print("Failed to snprintf_s.\n");
|
|
return NULL;
|
|
}
|
|
part_info = get_flash_partition_info(UAPI_FLASH_TYPE_NOR_0, devname, (td_u32)strlen(devname));
|
|
if (part_info == NULL) {
|
|
dbg_out("Can't get \"%s\" partition information.\n", devname);
|
|
return NULL;
|
|
}
|
|
return part_info;
|
|
}
|
|
|
|
|
|
static td_s32 spi_raw_init_part(td_s32 part, struct mtd_partition *ptn, struct mtd_info_user *mtdinfo)
|
|
{
|
|
td_s32 dev;
|
|
td_s32 readonly = ACCESS_NONE;
|
|
td_char buf[PATH_MAX];
|
|
uapi_flash_partinfo *part_info = NULL;
|
|
|
|
ptn->fd = INVALID_FD;
|
|
if (snprintf_s(buf, PATH_MAX, PATH_MAX - 1, DEV_MTDBASE"%d", part) == -1) {
|
|
soc_print("Failed to snprintf_s.\n");
|
|
return -1;
|
|
}
|
|
|
|
dev = spi_raw_prope(ptn, buf, sizeof(buf), &readonly);
|
|
if (dev < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (spi_raw_get_mtd_info(dev, mtdinfo) != TD_SUCCESS) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
|
|
part_info = spi_raw_get_part_info(part);
|
|
if (part_info == NULL) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
|
|
if (strncpy_s(ptn->mtddev, sizeof(ptn->mtddev), buf, strlen(buf)) != 0) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
ptn->mtddev[sizeof(ptn->mtddev) - 1] = '\0';
|
|
ptn->fd = dev;
|
|
ptn->readonly = readonly;
|
|
ptn->start = part_info->start_addr;
|
|
ptn->end = part_info->start_addr + mtdinfo->size - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static td_void spi_raw_init_info(td_s16 max_partition)
|
|
{
|
|
td_s32 ix;
|
|
struct mtd_info_user mtdinfo;
|
|
struct mtd_partition *ptn = g_spiraw_ctl->partition;
|
|
|
|
for (ix = 0; ix < max_partition; ix++) {
|
|
if (spi_raw_init_part(ix, ptn, &mtdinfo) < 0) {
|
|
continue;
|
|
}
|
|
|
|
g_spiraw_ctl->num_partition++;
|
|
if (g_spiraw_ctl->num_partition == 1) {
|
|
g_spiraw_ctl->pagesize = mtdinfo.writesize;
|
|
g_spiraw_ctl->blocksize = mtdinfo.erasesize;
|
|
g_spiraw_ctl->pagemask = (mtdinfo.writesize - 1);
|
|
g_spiraw_ctl->blockmask = (mtdinfo.erasesize - 1);
|
|
g_spiraw_ctl->oobsize = mtdinfo.oobsize;
|
|
g_spiraw_ctl->pageshift = (td_u32)offshift(mtdinfo.writesize);
|
|
g_spiraw_ctl->blockshift = (td_u32)offshift(mtdinfo.erasesize);
|
|
}
|
|
g_spiraw_ctl->size += mtdinfo.size;
|
|
ptn++;
|
|
}
|
|
}
|
|
|
|
td_s32 spi_raw_init(td_void)
|
|
{
|
|
td_s32 max_partition;
|
|
td_u32 spiraw_ctl_len;
|
|
|
|
if (g_spiraw_ctl != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
max_partition = get_max_partition();
|
|
if (max_partition < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (++max_partition >= MAX_PARTS) {
|
|
soc_print("partition maybe more than %d, please increase MAX_PARTS.\n", MAX_PARTS);
|
|
}
|
|
|
|
if (flash_partition_info_init() != 0) {
|
|
soc_print("Initial partition information failure.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (((sizeof(struct nand_raw_ctrl) + max_partition * sizeof(struct mtd_partition)) > MAX_U32) ||
|
|
(max_partition * sizeof(struct mtd_partition) > MAX_U32)) {
|
|
return -1;
|
|
}
|
|
|
|
spiraw_ctl_len = (td_u32)(sizeof(struct nand_raw_ctrl) + max_partition * sizeof(struct mtd_partition));
|
|
if (spiraw_ctl_len == 0) {
|
|
return -1;
|
|
}
|
|
|
|
g_spiraw_ctl = (struct nand_raw_ctrl *)malloc(spiraw_ctl_len);
|
|
if (g_spiraw_ctl == NULL) {
|
|
soc_print("Not enough memory.\n");
|
|
return -ENOMEM;
|
|
}
|
|
memset_s(g_spiraw_ctl, spiraw_ctl_len, 0, spiraw_ctl_len);
|
|
|
|
g_spiraw_ctl->num_partition = 0;
|
|
g_spiraw_ctl->size = 0;
|
|
|
|
spi_raw_init_info((td_s16)max_partition);
|
|
|
|
if (g_spiraw_ctl->num_partition == 0) {
|
|
dbg_out("Spi partition numbers is 0!\n");
|
|
free(g_spiraw_ctl);
|
|
g_spiraw_ctl = NULL;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
td_void spi_raw_get_info(td_u64 *totalsize, td_u32 *pagesize,
|
|
td_u32 *blocksize, td_u32 *oobsize, td_u32 *blockshift)
|
|
{
|
|
if (totalsize == NULL || pagesize == NULL || blocksize == NULL || \
|
|
oobsize == NULL || blockshift == NULL) {
|
|
return;
|
|
}
|
|
|
|
*totalsize = g_spiraw_ctl->size;
|
|
*pagesize = g_spiraw_ctl->pagesize;
|
|
*blocksize = g_spiraw_ctl->blocksize;
|
|
*oobsize = g_spiraw_ctl->oobsize;
|
|
*blockshift = g_spiraw_ctl->blockshift;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. if open SPI/NOR FLASH, return 0
|
|
* 2. if dev_name cannot match g_spiraw_ctl, return error_valid;
|
|
*/
|
|
td_u64 spi_raw_get_start_addr(const td_char *dev_name, td_ulong blocksize, td_s32 *value_valid)
|
|
{
|
|
struct mtd_partition *ptn = NULL;
|
|
td_s32 max_partition;
|
|
td_s32 ix;
|
|
|
|
if (value_valid == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (dev_name == NULL) {
|
|
*value_valid = 0;
|
|
return 0;
|
|
}
|
|
|
|
ptn = g_spiraw_ctl->partition;
|
|
|
|
max_partition = get_max_partition();
|
|
if (max_partition == 0) {
|
|
soc_print("Can't find mtd device at /dev/mtdx.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (g_spiraw_ctl->blocksize != blocksize) {
|
|
*value_valid = 1;
|
|
return 0;
|
|
}
|
|
|
|
for (ix = 0; ix <= max_partition; ix++) {
|
|
if (strncmp(ptn->mtddev, dev_name,
|
|
strlen(ptn->mtddev) > strlen(dev_name) ? strlen(ptn->mtddev) : strlen(dev_name)) == 0) {
|
|
break;
|
|
}
|
|
ptn++;
|
|
}
|
|
|
|
if (max_partition < ix) {
|
|
*value_valid = 0;
|
|
return 0;
|
|
}
|
|
/* lint -e661 */
|
|
*value_valid = 1;
|
|
/* lint +e661 */
|
|
return ptn->start;
|
|
}
|
|
|
|
static td_s32 spi_raw_rw_check(td_u64 offset, const td_u8 *buffer, td_ulong length)
|
|
{
|
|
if (g_spiraw_ctl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (buffer == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if ((offset >= g_spiraw_ctl->size) || (length == 0)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 spi_raw_permit(const struct mtd_partition *ptn,
|
|
td_u64 offset, td_u64 length, uapi_flash_access_perm perm)
|
|
{
|
|
if (ptn == NULL) {
|
|
soc_print("ptn is null\n");
|
|
return 0;
|
|
}
|
|
|
|
return ((ptn->start <= offset) && (offset < ptn->end) && (length != 0) &&
|
|
(((unsigned int)ptn->perm & (unsigned int)perm) != 0) && (ptn->fd != INVALID_FD));
|
|
}
|
|
|
|
static td_s32 spi_raw_read_block(struct mtd_partition *ptn, td_u64 *offset, td_u8 **buffer, td_ulong *length)
|
|
{
|
|
td_ulong num_read;
|
|
|
|
if (*offset + *length > ptn->end) {
|
|
num_read = (td_ulong)((ptn->end - *offset) + 1);
|
|
} else {
|
|
num_read = *length;
|
|
}
|
|
|
|
if (lseek(ptn->fd, (long)(*offset - ptn->start), SEEK_SET) != -1 &&
|
|
read(ptn->fd, *buffer, num_read) != num_read) {
|
|
soc_print("read \"%s\" fail. error(%d)\n", ptn->mtddev, errno);
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
*buffer += num_read;
|
|
*length -= num_read;
|
|
*offset += num_read;
|
|
|
|
return (td_s32)num_read;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. startaddr should be alignment with pagesize
|
|
* 2. this address maybe change when meet bad block
|
|
*/
|
|
td_s32 spi_raw_read(flash_rw_info_s *rw_info, td_u8 *buffer, td_ulong length, td_s32 skip_badblock)
|
|
{
|
|
td_s32 ix, rel;
|
|
td_s32 totalread = 0;
|
|
td_u64 offset;
|
|
|
|
(void)skip_badblock;
|
|
if (rw_info == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
offset = rw_info->start_addr;
|
|
|
|
if (spi_raw_rw_check(offset, buffer, length) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (ix = 0; ix < g_spiraw_ctl->num_partition && (length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_spiraw_ctl->partition[ix];
|
|
/* lint -save -e655 */
|
|
if (spi_raw_permit(ptn, offset, length, ACCESS_RD) != 0) {
|
|
rel = spi_raw_read_block(ptn, &offset, &buffer, &length);
|
|
if (rel < 0) {
|
|
return TD_FAILURE;
|
|
}
|
|
totalread += rel;
|
|
}
|
|
}
|
|
rw_info->start_addr = offset;
|
|
|
|
return totalread;
|
|
}
|
|
|
|
static td_s32 spi_raw_erase_check(td_u64 offset, td_u64 length)
|
|
{
|
|
if (g_spiraw_ctl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((offset >= g_spiraw_ctl->size) || (length == 0)) {
|
|
return -1;
|
|
}
|
|
|
|
if ((((td_ulong)offset & g_spiraw_ctl->blockmask) != 0) || (((td_ulong)length & g_spiraw_ctl->blockmask) != 0)) {
|
|
soc_print("offset or length should be alignment with blocksize(0x%X)\n", (td_u32)g_spiraw_ctl->blocksize);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static td_s64 spi_raw_erase_block(struct mtd_partition *ptn, td_u64 *offset, td_u64 *length)
|
|
{
|
|
struct erase_info_user64 eraseinfo;
|
|
|
|
eraseinfo.start = (td_u64)(*offset - ptn->start);
|
|
if (*offset + *length > ptn->end) {
|
|
eraseinfo.length = (td_u64)((ptn->end - *offset) + 1);
|
|
} else {
|
|
eraseinfo.length = *length;
|
|
}
|
|
|
|
/* don't deal with */
|
|
if (ioctl(ptn->fd, MEMERASE64, &eraseinfo) != 0) {
|
|
soc_print("Erase 0x%llx failed!\n", *offset);
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
*length -= eraseinfo.length;
|
|
*offset += eraseinfo.length;
|
|
return (td_s64)eraseinfo.length;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. offset and length should be alignment with blocksize
|
|
*/
|
|
td_s64 spi_raw_erase(td_s32 fd, td_u64 startaddr, td_u64 length, td_u64 openaddr, td_u64 limit_len)
|
|
{
|
|
td_s32 ix;
|
|
td_s64 totalerase = 0;
|
|
td_u64 offset = startaddr;
|
|
td_u64 temp_length = length;
|
|
td_s64 rel;
|
|
|
|
TD_UNUSED(openaddr);
|
|
TD_UNUSED(fd);
|
|
TD_UNUSED(limit_len);
|
|
|
|
if (spi_raw_erase_check(offset, temp_length) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (offset + temp_length > g_spiraw_ctl->size) {
|
|
temp_length = g_spiraw_ctl->size - offset;
|
|
}
|
|
|
|
for (ix = 0; ix < g_spiraw_ctl->num_partition && (temp_length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_spiraw_ctl->partition[ix];
|
|
|
|
if (spi_raw_permit(ptn, offset, temp_length, ACCESS_WR) != 0) {
|
|
if (ptn->readonly != 0) {
|
|
soc_print("erase a read only partition \"%s\".\n", ptn->mtddev);
|
|
return -1;
|
|
}
|
|
|
|
rel = spi_raw_erase_block(ptn, &offset, &temp_length);
|
|
if (rel < 0) {
|
|
return -1;
|
|
}
|
|
totalerase += rel;
|
|
}
|
|
}
|
|
return totalerase;
|
|
}
|
|
|
|
static td_s32 spi_raw_write_block(struct mtd_partition *ptn, td_u64 *offset, td_u8 **buffer, td_ulong *length)
|
|
{
|
|
td_s32 num_write;
|
|
|
|
if (*offset + *length > ptn->end) {
|
|
num_write = (td_s32)((ptn->end - *offset) + 1);
|
|
} else {
|
|
num_write = (td_s32)*length;
|
|
}
|
|
|
|
if (lseek(ptn->fd, (off_t)(*offset - ptn->start), SEEK_SET) != -1 &&
|
|
write(ptn->fd, *buffer, (size_t)num_write) != (ssize_t)num_write) {
|
|
soc_print("write \"%s\" fail. error(%d)\n", ptn->mtddev, errno);
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
*buffer += num_write;
|
|
*length -= (td_u32)num_write;
|
|
*offset += (td_ulong)num_write;
|
|
|
|
return num_write;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. startaddr should be alignment with pagesize
|
|
*/
|
|
td_s32 spi_raw_write(flash_rw_info_s *rw_info, td_u8 *buffer, td_ulong length)
|
|
{
|
|
td_s32 ix, rel;
|
|
td_s32 totalwrite = 0;
|
|
td_u64 offset;
|
|
|
|
if (rw_info == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
offset = rw_info->start_addr;
|
|
|
|
if (spi_raw_rw_check(offset, buffer, length) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (ix = 0; ix < g_spiraw_ctl->num_partition && (length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_spiraw_ctl->partition[ix];
|
|
|
|
if (spi_raw_permit(ptn, offset, length, ACCESS_WR) != 0) {
|
|
if (ptn->readonly != 0) {
|
|
soc_print("Write a read only partition \"%s\".\n", ptn->mtddev);
|
|
return -1;
|
|
}
|
|
|
|
rel = spi_raw_write_block(ptn, &offset, &buffer, &length);
|
|
if (rel < 0) {
|
|
return TD_FAILURE;
|
|
}
|
|
totalwrite += rel;
|
|
}
|
|
}
|
|
|
|
rw_info->start_addr = offset;
|
|
return totalwrite;
|
|
}
|
|
|
|
td_s32 spi_raw_dump_partition(td_void)
|
|
{
|
|
td_s32 ix;
|
|
struct mtd_partition *ptn = NULL;
|
|
|
|
if (g_spiraw_ctl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
soc_print("-------------------------\n");
|
|
soc_print("mtd device start length mode\n");
|
|
for (ix = 0; ix < g_spiraw_ctl->num_partition; ix++) {
|
|
ptn = &g_spiraw_ctl->partition[ix];
|
|
if (((unsigned int)ptn->perm & ACCESS_RD) == ACCESS_RD) {
|
|
soc_print("%-12s ", ptn->mtddev);
|
|
soc_print("%5s ", int_to_size(ptn->start));
|
|
soc_print("%6s ", int_to_size(ptn->end + 1 - ptn->start));
|
|
soc_print("%2s ", ptn->readonly != 0 ? "r" : "rw");
|
|
soc_print("\n");
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
td_s32 spi_raw_destroy(td_void)
|
|
{
|
|
td_s32 ix;
|
|
|
|
if (g_spiraw_ctl == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
for (ix = 0; ix < g_spiraw_ctl->num_partition; ix++) {
|
|
if (g_spiraw_ctl->partition[ix].fd != INVALID_FD) {
|
|
close(g_spiraw_ctl->partition[ix].fd);
|
|
}
|
|
}
|
|
|
|
free(g_spiraw_ctl);
|
|
g_spiraw_ctl = NULL;
|
|
return 0;
|
|
}
|
|
|