mirror of
https://github.com/torvalds/linux.git
synced 2026-06-07 22:14:04 +02:00
drivers: rkflash: add rkflash drivers
Change-Id: I6faf12d63088b8df345b69fc4665915429c856c9 Signed-off-by: Dingqiang Lin <jon.lin@rock-chips.com>
This commit is contained in:
parent
4f7c7cdaef
commit
a38fd055d2
|
|
@ -202,6 +202,8 @@ source "drivers/fpga/Kconfig"
|
|||
|
||||
source "drivers/tee/Kconfig"
|
||||
|
||||
source "drivers/rkflash/Kconfig"
|
||||
|
||||
source "drivers/rk_nand/Kconfig"
|
||||
|
||||
source "drivers/headset_observe/Kconfig"
|
||||
|
|
|
|||
|
|
@ -178,3 +178,5 @@ obj-$(CONFIG_FPGA) += fpga/
|
|||
obj-$(CONFIG_TEE) += tee/
|
||||
obj-$(CONFIG_RK_NAND) += rk_nand/
|
||||
obj-$(CONFIG_RK_HEADSET) += headset_observe/
|
||||
obj-$(CONFIG_RK_FLASH) += rkflash/
|
||||
obj-y += rk_nand/
|
||||
|
|
|
|||
59
drivers/rkflash/Kconfig
Normal file
59
drivers/rkflash/Kconfig
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
if ARCH_ROCKCHIP
|
||||
|
||||
menuconfig RK_FLASH
|
||||
tristate "Rockchip Flash Devices Support"
|
||||
default n
|
||||
help
|
||||
Enable rockchip flash devices support.
|
||||
|
||||
rkflash driver support 3-type flash devices: NANDC NAND, SFC_NOR
|
||||
and SFC_NAND.
|
||||
|
||||
Say Y when you have a board with one of them.
|
||||
|
||||
if RK_FLASH
|
||||
|
||||
comment "Rockchip Flash Devices"
|
||||
|
||||
config RK_NANDC_NAND
|
||||
tristate "RK NANDC NAND Device Support"
|
||||
default n
|
||||
help
|
||||
Enable NANDC_NAND device support.
|
||||
It's block interface.
|
||||
Say Y when you have a board with nand flash supported by rockchip
|
||||
nandc controller.
|
||||
|
||||
config RK_SFC_NOR
|
||||
tristate "RK SFC NOR Device Support"
|
||||
default n
|
||||
help
|
||||
Enable SFC_NOR device support.
|
||||
It's block interface.
|
||||
Say Y when you have a board with nor flash supported by rockchip
|
||||
sfc controller.
|
||||
|
||||
config RK_SFC_NAND
|
||||
tristate "RK SFC NAND Device Support"
|
||||
default n
|
||||
help
|
||||
Enable SFC_NAND device support.
|
||||
It's block interface
|
||||
Say Y when you have a board with nand flash supported by rockchip
|
||||
sfc controller.
|
||||
|
||||
config RK_SFC_NOR_MTD
|
||||
bool "RK SFC NOR mtd Interface Support"
|
||||
depends on RK_SFC_NOR
|
||||
default n
|
||||
help
|
||||
Enable mtd interface for SFC_NOR device.
|
||||
It's mtd block interface.
|
||||
Say Y when you have a board with mtd interface nor flash supported
|
||||
by rockchip sfc controller.
|
||||
|
||||
endif # RK_FLASH
|
||||
|
||||
endif # ARCH_ROCKCHIP
|
||||
6
drivers/rkflash/Makefile
Normal file
6
drivers/rkflash/Makefile
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_RK_NANDC_NAND) += rkflash_blk.o rkflash_debug.o rknandc_base.o nand_boot.o flash.o nandc.o ftl_flash_plat.o rk_sftl.o
|
||||
obj-$(CONFIG_RK_SFC_NOR) += rkflash_blk.o rkflash_debug.o rksfc_base.o sfc_nor_boot.o sfc_nor.o sfc.o
|
||||
obj-$(CONFIG_RK_SFC_NOR_MTD) += sfc_nor_mtd.o
|
||||
obj-$(CONFIG_RK_SFC_NAND) += rkflash_blk.o rkflash_debug.o rksfc_base.o sfc_nand_boot.o sfc_nand.o sfc.o ftl_flash_plat.o rk_sftl.o
|
||||
500
drivers/rkflash/flash.c
Normal file
500
drivers/rkflash/flash.c
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "flash.h"
|
||||
#include "flash_com.h"
|
||||
#include "nandc.h"
|
||||
|
||||
#define FLASH_STRESS_TEST_EN 0
|
||||
|
||||
static u8 id_byte[MAX_FLASH_NUM][8];
|
||||
static u8 die_cs_index[MAX_FLASH_NUM];
|
||||
static u8 g_nand_max_die;
|
||||
static u16 g_totle_block;
|
||||
static u8 g_nand_flash_ecc_bits;
|
||||
static u8 g_nand_idb_res_blk_num;
|
||||
|
||||
static struct NAND_PARA_INFO_T nand_para = {
|
||||
2,
|
||||
{0x98, 0xF1, 0, 0, 0, 0},
|
||||
TOSHIBA,
|
||||
1,
|
||||
4,
|
||||
64,
|
||||
1,
|
||||
1,
|
||||
1024,
|
||||
0x100,
|
||||
LSB_0,
|
||||
RR_NONE,
|
||||
16,
|
||||
40,
|
||||
1,
|
||||
0,
|
||||
BBF_1,
|
||||
MPM_0,
|
||||
{0}
|
||||
}; /* TC58NVG0S3HTA00 */
|
||||
|
||||
void nandc_flash_reset(u8 cs)
|
||||
{
|
||||
nandc_flash_cs(cs);
|
||||
nandc_writel(RESET_CMD, NANDC_CHIP_CMD(cs));
|
||||
nandc_wait_flash_ready(cs);
|
||||
nandc_flash_de_cs(cs);
|
||||
}
|
||||
|
||||
static void flash_read_id_raw(u8 cs, u8 *buf)
|
||||
{
|
||||
u8 *ptr = (u8 *)buf;
|
||||
|
||||
nandc_flash_reset(cs);
|
||||
nandc_flash_cs(cs);
|
||||
nandc_writel(READ_ID_CMD, NANDC_CHIP_CMD(cs));
|
||||
nandc_writel(0x00, NANDC_CHIP_ADDR(cs));
|
||||
nandc_delayns(200);
|
||||
|
||||
ptr[0] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[1] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[2] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[3] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[4] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[5] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[6] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
ptr[7] = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
|
||||
nandc_flash_de_cs(cs);
|
||||
if (ptr[0] != 0xFF && ptr[0] && ptr[1] != 0xFF)
|
||||
PRINT_E("No.%d FLASH ID:%x %x %x %x %x %x\n",
|
||||
cs + 1, ptr[0], ptr[1], ptr[2],
|
||||
ptr[3], ptr[4], ptr[5]);
|
||||
}
|
||||
|
||||
static void flash_bch_sel(u8 bits)
|
||||
{
|
||||
g_nand_flash_ecc_bits = bits;
|
||||
nandc_bch_sel(bits);
|
||||
}
|
||||
|
||||
static __maybe_unused void flash_timing_cfg(u32 ahb_khz)
|
||||
{
|
||||
nandc_time_cfg(nand_para.access_freq);
|
||||
}
|
||||
|
||||
static void flash_read_cmd(u8 cs, u32 page_addr)
|
||||
{
|
||||
nandc_writel(READ_CMD >> 8, NANDC_CHIP_CMD(cs));
|
||||
nandc_writel(0x00, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(0x00, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr & 0x00ff, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 8, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 16, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(READ_CMD & 0x00ff, NANDC_CHIP_CMD(cs));
|
||||
}
|
||||
|
||||
static void flash_prog_first_cmd(u8 cs, u32 page_addr)
|
||||
{
|
||||
nandc_writel(PAGE_PROG_CMD >> 8, NANDC_CHIP_CMD(cs));
|
||||
nandc_writel(0x00, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(0x00, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr & 0x00ff, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 8, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 16, NANDC_CHIP_ADDR(cs));
|
||||
}
|
||||
|
||||
static void flash_erase_cmd(u8 cs, u32 page_addr)
|
||||
{
|
||||
nandc_writel(BLOCK_ERASE_CMD >> 8, NANDC_CHIP_CMD(cs));
|
||||
nandc_writel(page_addr & 0x00ff, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 8, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 16, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(BLOCK_ERASE_CMD & 0x00ff, NANDC_CHIP_CMD(cs));
|
||||
}
|
||||
|
||||
static void flash_prog_second_cmd(u8 cs, u32 page_addr)
|
||||
{
|
||||
nandc_writel(PAGE_PROG_CMD & 0x00ff, NANDC_CHIP_CMD(cs));
|
||||
}
|
||||
|
||||
static u32 flash_read_status(u8 cs, u32 page_addr)
|
||||
{
|
||||
nandc_writel(READ_STATUS_CMD, NANDC_CHIP_CMD(cs));
|
||||
nandc_delayns(80);
|
||||
|
||||
return nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
}
|
||||
|
||||
static void flash_read_random_dataout_cmd(u8 cs, u32 col_addr)
|
||||
{
|
||||
nandc_writel(READ_DP_OUT_CMD >> 8, NANDC_CHIP_CMD(cs));
|
||||
nandc_writel(col_addr & 0x00ff, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(col_addr >> 8, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(READ_DP_OUT_CMD & 0x00ff, NANDC_CHIP_CMD(cs));
|
||||
}
|
||||
|
||||
static u32 flash_read_page_raw(u8 cs, u32 page_addr, u32 *p_data, u32 *p_spare)
|
||||
{
|
||||
u32 ret = 0;
|
||||
u32 error_ecc_bits;
|
||||
u32 sec_per_page = nand_para.sec_per_page;
|
||||
|
||||
nandc_wait_flash_ready(cs);
|
||||
nandc_flash_cs(cs);
|
||||
flash_read_cmd(cs, page_addr);
|
||||
nandc_wait_flash_ready(cs);
|
||||
flash_read_random_dataout_cmd(cs, 0);
|
||||
nandc_wait_flash_ready(cs);
|
||||
|
||||
error_ecc_bits = nandc_xfer_data(cs, NANDC_READ, sec_per_page,
|
||||
p_data, p_spare);
|
||||
if (error_ecc_bits > 2) {
|
||||
PRINT_E("FlashReadRawPage %x %x error_ecc_bits %d\n",
|
||||
cs, page_addr, error_ecc_bits);
|
||||
if (p_data)
|
||||
rknand_print_hex("data:", p_data, 4, 8);
|
||||
if (p_spare)
|
||||
rknand_print_hex("spare:", p_spare, 4, 2);
|
||||
}
|
||||
nandc_flash_de_cs(cs);
|
||||
|
||||
if (error_ecc_bits != NAND_STS_ECC_ERR) {
|
||||
if (error_ecc_bits >= (u32)nand_para.ecc_bits - 3)
|
||||
ret = NAND_STS_REFRESH;
|
||||
else
|
||||
ret = NAND_STS_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u32 flash_read_page(u8 cs, u32 page_addr, u32 *p_data, u32 *p_spare)
|
||||
{
|
||||
u32 ret;
|
||||
|
||||
ret = flash_read_page_raw(cs, page_addr, p_data, p_spare);
|
||||
if (ret == NAND_STS_ECC_ERR)
|
||||
ret = flash_read_page_raw(cs, page_addr, p_data, p_spare);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u32 flash_prog_page(u8 cs, u32 page_addr, u32 *p_data, u32 *p_spare)
|
||||
{
|
||||
u32 status;
|
||||
u32 sec_per_page = nand_para.sec_per_page;
|
||||
|
||||
nandc_wait_flash_ready(cs);
|
||||
nandc_flash_cs(cs);
|
||||
flash_prog_first_cmd(cs, page_addr);
|
||||
nandc_xfer_data(cs, NANDC_WRITE, sec_per_page, p_data, p_spare);
|
||||
flash_prog_second_cmd(cs, page_addr);
|
||||
nandc_wait_flash_ready(cs);
|
||||
status = flash_read_status(cs, page_addr);
|
||||
nandc_flash_de_cs(cs);
|
||||
status &= 0x01;
|
||||
if (status)
|
||||
PRINT_I("%s addr=%x status=%x\n", __func__,
|
||||
page_addr, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static u32 flash_erase_block(u8 cs, u32 page_addr)
|
||||
{
|
||||
u32 status;
|
||||
|
||||
nandc_wait_flash_ready(cs);
|
||||
nandc_flash_cs(cs);
|
||||
flash_erase_cmd(cs, page_addr);
|
||||
nandc_wait_flash_ready(cs);
|
||||
status = flash_read_status(cs, page_addr);
|
||||
nandc_flash_de_cs(cs);
|
||||
status &= 0x01;
|
||||
if (status)
|
||||
PRINT_I("%s addr=%x status=%x\n", __func__,
|
||||
page_addr, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void flash_read_spare(u8 cs, u32 page_addr, u8 *spare)
|
||||
{
|
||||
u32 col = nand_para.sec_per_page << 9;
|
||||
|
||||
nandc_writel(READ_CMD >> 8, NANDC_CHIP_CMD(cs));
|
||||
nandc_writel(col, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(col >> 8, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr & 0x00ff, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 8, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(page_addr >> 16, NANDC_CHIP_ADDR(cs));
|
||||
nandc_writel(READ_CMD & 0x00ff, NANDC_CHIP_CMD(cs));
|
||||
|
||||
nandc_wait_flash_ready(cs);
|
||||
|
||||
*spare = nandc_readl(NANDC_CHIP_DATA(cs));
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the 1st page's 1st spare byte of a phy_blk
|
||||
* If not FF, it's bad blk
|
||||
*/
|
||||
static s32 get_bad_blk_list(u16 *table, u32 die)
|
||||
{
|
||||
u16 blk;
|
||||
u32 bad_cnt, page_addr0, page_addr1, page_addr2;
|
||||
u32 blk_per_die;
|
||||
u8 bad_flag0, bad_flag1, bad_flag2;
|
||||
|
||||
bad_cnt = 0;
|
||||
blk_per_die = nand_para.plane_per_die * nand_para.blk_per_plane;
|
||||
for (blk = 0; blk < blk_per_die; blk++) {
|
||||
bad_flag0 = 0xFF;
|
||||
bad_flag1 = 0xFF;
|
||||
bad_flag2 = 0xFF;
|
||||
page_addr0 = (blk + blk_per_die * die) *
|
||||
nand_para.page_per_blk + 0;
|
||||
page_addr1 = page_addr0 + 1;
|
||||
page_addr2 = page_addr0 + nand_para.page_per_blk - 1;
|
||||
flash_read_spare(die, page_addr0, &bad_flag0);
|
||||
flash_read_spare(die, page_addr1, &bad_flag1);
|
||||
flash_read_spare(die, page_addr2, &bad_flag2);
|
||||
if (bad_flag0 != 0xFF ||
|
||||
bad_flag1 != 0xFF ||
|
||||
bad_flag2 != 0xFF) {
|
||||
table[bad_cnt++] = blk;
|
||||
PRINT_E("die[%d], bad_blk[%d]\n", die, blk);
|
||||
}
|
||||
}
|
||||
return bad_cnt;
|
||||
}
|
||||
|
||||
#if FLASH_STRESS_TEST_EN
|
||||
|
||||
#define FLASH_PAGE_SIZE 2048
|
||||
#define FLASH_SPARE_SIZE 8
|
||||
|
||||
static u16 bad_blk_list[1024];
|
||||
static u32 pwrite[FLASH_PAGE_SIZE / 4];
|
||||
static u32 pread[FLASH_PAGE_SIZE / 4];
|
||||
static u32 pspare_write[FLASH_SPARE_SIZE / 4];
|
||||
static u32 pspare_read[FLASH_SPARE_SIZE / 4];
|
||||
static u32 bad_blk_num;
|
||||
static u32 bad_page_num;
|
||||
|
||||
static void flash_test(void)
|
||||
{
|
||||
u32 i, blk, page, bad_cnt, page_addr;
|
||||
int ret;
|
||||
u32 pages_num = 64;
|
||||
u32 blk_addr = 64;
|
||||
u32 is_bad_blk = 0;
|
||||
|
||||
PRINT_E("%s\n", __func__);
|
||||
|
||||
bad_blk_num = 0;
|
||||
bad_page_num = 0;
|
||||
bad_cnt = get_bad_blk_list(bad_blk_list, 0);
|
||||
|
||||
for (blk = 0; blk < 1024; blk++) {
|
||||
for (i = 0; i < bad_cnt; i++) {
|
||||
if (bad_blk_list[i] == blk)
|
||||
break;
|
||||
}
|
||||
if (i < bad_cnt)
|
||||
continue;
|
||||
is_bad_blk = 0;
|
||||
PRINT_E("Flash prog block: %x\n", blk);
|
||||
flash_erase_block(0, blk * blk_addr);
|
||||
for (page = 0; page < pages_num; page++) {
|
||||
page_addr = blk * blk_addr + page;
|
||||
for (i = 0; i < 512; i++)
|
||||
pwrite[i] = (page_addr << 16) + i;
|
||||
pspare_write[0] = pwrite[0] + 0x5AF0;
|
||||
pspare_write[1] = pspare_write[0] + 1;
|
||||
flash_prog_page(0, page_addr, pwrite, pspare_write);
|
||||
memset(pread, 0, 2048);
|
||||
memset(pspare_read, 0, 8);
|
||||
ret = flash_read_page(0, page_addr, pread,
|
||||
pspare_read);
|
||||
if (ret != NAND_STS_OK)
|
||||
is_bad_blk = 1;
|
||||
for (i = 0; i < 512; i++) {
|
||||
if (pwrite[i] != pread[i]) {
|
||||
is_bad_blk = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (pspare_write[i] != pspare_read[i]) {
|
||||
is_bad_blk = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_bad_blk) {
|
||||
bad_page_num++;
|
||||
PRINT_E("ERR:page%x, ret=%x\n", page_addr, ret);
|
||||
rknand_print_hex("data:", pread, 4, 8);
|
||||
rknand_print_hex("spare:", pspare_read, 4, 2);
|
||||
}
|
||||
}
|
||||
flash_erase_block(0, blk * blk_addr);
|
||||
if (is_bad_blk)
|
||||
bad_blk_num++;
|
||||
}
|
||||
PRINT_E("bad_blk_num = %d, bad_page_num = %d\n",
|
||||
bad_blk_num, bad_page_num);
|
||||
|
||||
PRINT_E("Flash Test Finish!!!\n");
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void flash_die_info_init(void)
|
||||
{
|
||||
u32 cs;
|
||||
|
||||
g_nand_max_die = 0;
|
||||
for (cs = 0; cs < MAX_FLASH_NUM; cs++) {
|
||||
if (nand_para.nand_id[1] == id_byte[cs][1]) {
|
||||
die_cs_index[g_nand_max_die] = cs;
|
||||
g_nand_max_die++;
|
||||
}
|
||||
}
|
||||
g_totle_block = g_nand_max_die * nand_para.plane_per_die *
|
||||
nand_para.blk_per_plane;
|
||||
}
|
||||
|
||||
static void flash_print_info(void)
|
||||
{
|
||||
PRINT_I("No.0 FLASH ID: %x %x %x %x %x %x\n",
|
||||
nand_para.nand_id[0],
|
||||
nand_para.nand_id[1],
|
||||
nand_para.nand_id[2],
|
||||
nand_para.nand_id[3],
|
||||
nand_para.nand_id[4],
|
||||
nand_para.nand_id[5]);
|
||||
PRINT_I("die_per_chip: %x\n", nand_para.die_per_chip);
|
||||
PRINT_I("sec_per_page: %x\n", nand_para.sec_per_page);
|
||||
PRINT_I("page_per_blk: %x\n", nand_para.page_per_blk);
|
||||
PRINT_I("cell: %x\n", nand_para.cell);
|
||||
PRINT_I("plane_per_die: %x\n", nand_para.plane_per_die);
|
||||
PRINT_I("blk_per_plane: %x\n", nand_para.blk_per_plane);
|
||||
PRINT_I("TotleBlock: %x\n", g_totle_block);
|
||||
PRINT_I("die gap: %x\n", nand_para.die_gap);
|
||||
PRINT_I("lsb_mode: %x\n", nand_para.lsb_mode);
|
||||
PRINT_I("read_retry_mode: %x\n", nand_para.read_retry_mode);
|
||||
PRINT_I("ecc_bits: %x\n", nand_para.ecc_bits);
|
||||
PRINT_I("Use ecc_bits: %x\n", g_nand_flash_ecc_bits);
|
||||
PRINT_I("access_freq: %x\n", nand_para.access_freq);
|
||||
PRINT_I("opt_mode: %x\n", nand_para.opt_mode);
|
||||
|
||||
PRINT_I("Cache read enable: %x\n",
|
||||
nand_para.operation_opt & NAND_CACHE_READ_EN ? 1 : 0);
|
||||
PRINT_I("Cache random read enable: %x\n",
|
||||
nand_para.operation_opt & NAND_CACHE_RANDOM_READ_EN ? 1 : 0);
|
||||
PRINT_I("Cache prog enable: %x\n",
|
||||
nand_para.operation_opt & NAND_CACHE_PROG_EN ? 1 : 0);
|
||||
PRINT_I("multi read enable: %x\n",
|
||||
nand_para.operation_opt & NAND_MULTI_READ_EN ? 1 : 0);
|
||||
|
||||
PRINT_I("multi prog enable: %x\n",
|
||||
nand_para.operation_opt & NAND_MULTI_PROG_EN ? 1 : 0);
|
||||
PRINT_I("interleave enable: %x\n",
|
||||
nand_para.operation_opt & NAND_INTERLEAVE_EN ? 1 : 0);
|
||||
|
||||
PRINT_I("read retry enable: %x\n",
|
||||
nand_para.operation_opt & NAND_READ_RETRY_EN ? 1 : 0);
|
||||
PRINT_I("randomizer enable: %x\n",
|
||||
nand_para.operation_opt & NAND_RANDOMIZER_EN ? 1 : 0);
|
||||
|
||||
PRINT_I("SDR enable: %x\n",
|
||||
nand_para.operation_opt & NAND_SDR_EN ? 1 : 0);
|
||||
PRINT_I("ONFI enable: %x\n",
|
||||
nand_para.operation_opt & NAND_ONFI_EN ? 1 : 0);
|
||||
PRINT_I("TOGGLE enable: %x\n",
|
||||
nand_para.operation_opt & NAND_TOGGLE_EN ? 1 : 0);
|
||||
|
||||
PRINT_I("g_nand_idb_res_blk_num: %x\n", g_nand_idb_res_blk_num);
|
||||
}
|
||||
|
||||
static void ftl_flash_init(void)
|
||||
{
|
||||
/* para init */
|
||||
g_nand_phy_info.nand_type = nand_para.cell;
|
||||
g_nand_phy_info.die_num = nand_para.die_per_chip;
|
||||
g_nand_phy_info.plane_per_die = nand_para.plane_per_die;
|
||||
g_nand_phy_info.blk_per_plane = nand_para.blk_per_plane;
|
||||
g_nand_phy_info.page_per_blk = nand_para.page_per_blk;
|
||||
g_nand_phy_info.page_per_slc_blk = nand_para.page_per_blk /
|
||||
nand_para.cell;
|
||||
g_nand_phy_info.byte_per_sec = 512;
|
||||
g_nand_phy_info.sec_per_page = nand_para.sec_per_page;
|
||||
g_nand_phy_info.sec_per_blk = nand_para.sec_per_page *
|
||||
nand_para.page_per_blk;
|
||||
g_nand_phy_info.reserved_blk = 8;
|
||||
g_nand_phy_info.blk_per_die = nand_para.plane_per_die *
|
||||
nand_para.blk_per_plane;
|
||||
g_nand_phy_info.ecc_bits = nand_para.ecc_bits;
|
||||
|
||||
/* driver register */
|
||||
g_nand_ops.get_bad_blk_list = get_bad_blk_list;
|
||||
g_nand_ops.erase_blk = flash_erase_block;
|
||||
g_nand_ops.prog_page = flash_prog_page;
|
||||
g_nand_ops.read_page = flash_read_page;
|
||||
}
|
||||
|
||||
u32 nandc_flash_init(void __iomem *nandc_addr)
|
||||
{
|
||||
u32 cs;
|
||||
|
||||
/* PRINT_I("...%s enter...\n", __func__); */
|
||||
g_nand_idb_res_blk_num = MAX_IDB_RESERVED_BLOCK;
|
||||
|
||||
nandc_init(nandc_addr);
|
||||
|
||||
for (cs = 0; cs < MAX_FLASH_NUM; cs++) {
|
||||
flash_read_id_raw(cs, id_byte[cs]);
|
||||
if (cs == 0) {
|
||||
if (id_byte[0][0] == 0xFF ||
|
||||
id_byte[0][0] == 0 ||
|
||||
id_byte[0][1] == 0xFF)
|
||||
return FTL_NO_FLASH;
|
||||
if (id_byte[0][1] != 0xF1 &&
|
||||
id_byte[0][1] != 0xDA &&
|
||||
id_byte[0][1] != 0xD1 &&
|
||||
id_byte[0][1] != 0x95)
|
||||
return FTL_UNSUPPORTED_FLASH;
|
||||
}
|
||||
}
|
||||
nand_para.nand_id[1] = id_byte[0][1];
|
||||
if (id_byte[0][1] == 0xDA) {
|
||||
nand_para.plane_per_die = 2;
|
||||
nand_para.nand_id[1] = 0xDA;
|
||||
}
|
||||
flash_die_info_init();
|
||||
flash_bch_sel(nand_para.ecc_bits);
|
||||
flash_print_info();
|
||||
/* flash_print_info(); */
|
||||
ftl_flash_init();
|
||||
|
||||
#if FLASH_STRESS_TEST_EN
|
||||
flash_test();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nandc_flash_get_id(u8 cs, void *buf)
|
||||
{
|
||||
memcpy(buf, id_byte[cs], 5);
|
||||
}
|
||||
|
||||
u32 nandc_flash_deinit(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
143
drivers/rkflash/flash.h
Normal file
143
drivers/rkflash/flash.h
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __FLASH_H
|
||||
#define __FLASH_H
|
||||
|
||||
#include "typedef.h"
|
||||
|
||||
#ifndef BIT
|
||||
#define BIT(nr) (1 << (nr))
|
||||
#endif
|
||||
|
||||
#define MAX_FLASH_NUM 2
|
||||
#define MAX_IDB_RESERVED_BLOCK 12
|
||||
|
||||
#define NAND_CACHE_READ_EN BIT(0)
|
||||
#define NAND_CACHE_RANDOM_READ_EN BIT(1)
|
||||
#define NAND_CACHE_PROG_EN BIT(2)
|
||||
#define NAND_MULTI_READ_EN BIT(3)
|
||||
|
||||
#define NAND_MULTI_PROG_EN BIT(4)
|
||||
#define NAND_INTERLEAVE_EN BIT(5)
|
||||
#define NAND_READ_RETRY_EN BIT(6)
|
||||
#define NAND_RANDOMIZER_EN BIT(7)
|
||||
|
||||
#define NAND_INTER_MODE_OFFSET (0x8)
|
||||
#define NAND_INTER_MODE_MARK (0x07)
|
||||
#define NAND_INTER_SDR_EN BIT(0)
|
||||
#define NAND_INTER_ONFI_EN BIT(1)
|
||||
#define NAND_INTER_TOGGLE_EN BIT(2)
|
||||
|
||||
#define NAND_SDR_EN BIT(8)
|
||||
#define NAND_ONFI_EN BIT(9)
|
||||
#define NAND_TOGGLE_EN BIT(10)
|
||||
#define NAND_UNIQUE_ID_EN BIT(11)
|
||||
|
||||
#define RESET_CMD 0xff
|
||||
#define READ_ID_CMD 0x90
|
||||
#define READ_STATUS_CMD 0x70
|
||||
#define PAGE_PROG_CMD 0x8010
|
||||
#define BLOCK_ERASE_CMD 0x60d0
|
||||
#define READ_CMD 0x0030
|
||||
#define READ_DP_OUT_CMD 0x05E0
|
||||
|
||||
#define SAMSUNG 0x00 /* SAMSUNG */
|
||||
#define TOSHIBA 0x01 /* TOSHIBA */
|
||||
#define HYNIX 0x02 /* HYNIX */
|
||||
#define INFINEON 0x03 /* INFINEON */
|
||||
#define MICRON 0x04 /* MICRON */
|
||||
#define RENESAS 0x05 /* RENESAS */
|
||||
#define ST 0x06 /* ST */
|
||||
#define INTEL 0x07 /* intel */
|
||||
#define Sandisk 0x08 /* Sandisk */
|
||||
|
||||
#define RR_NONE 0x00
|
||||
#define RR_HY_1 0x01 /* hynix H27UCG8T2M */
|
||||
#define RR_HY_2 0x02 /* hynix H27UBG08U0B */
|
||||
#define RR_HY_3 0x03 /* hynix H27UCG08U0B H27UBG08U0C */
|
||||
#define RR_HY_4 0x04 /* hynix H27UCG8T2A */
|
||||
#define RR_HY_5 0x05 /* hynix H27UCG8T2E */
|
||||
#define RR_HY_6 0x06 /* hynix H27QCG8T2F5R-BCG */
|
||||
#define RR_MT_1 0x11 /* micron */
|
||||
#define RR_MT_2 0x12 /* micron L94C L95B */
|
||||
#define RR_TH_1 0x21 /* toshiba */
|
||||
#define RR_TH_2 0x22 /* toshiba */
|
||||
#define RR_TH_3 0x23 /* toshiba */
|
||||
#define RR_SS_1 0x31 /* samsung */
|
||||
#define RR_SD_1 0x41 /* Sandisk */
|
||||
#define RR_SD_2 0x42 /* Sandisk */
|
||||
#define RR_SD_3 0x43 /* Sandisk */
|
||||
#define RR_SD_4 0x44 /* Sandisk */
|
||||
|
||||
/* 0 1 2 3 4 5 6 7 8 9 slc */
|
||||
#define LSB_0 0
|
||||
/* 0 1 2 3 6 7 A B E F hynix, micron 74A */
|
||||
#define LSB_1 1
|
||||
/* 0 1 3 5 7 9 B D toshiba samsung sandisk */
|
||||
#define LSB_2 2
|
||||
/* 0 1 2 3 4 5 8 9 C D 10 11 micron 84A */
|
||||
#define LSB_3 3
|
||||
/* 0 1 2 3 4 5 7 8 A B E F micron L95B */
|
||||
#define LSB_4 4
|
||||
/* 0 1 2 3 4 5 8 9 14 15 20 21 26 27 micron B74A TLC */
|
||||
#define LSB_6 6
|
||||
/* 0 3 6 9 C F 12 15 18 15 1B 1E 21 24 K9ABGD8U0C TLC */
|
||||
#define LSB_7 7
|
||||
|
||||
/* BadBlockFlagMode */
|
||||
/* first spare @ first page of each blocks */
|
||||
#define BBF_1 1
|
||||
/* first spare @ last page of each blocks */
|
||||
#define BBF_2 2
|
||||
/* first spare @ first and last page of each blocks */
|
||||
#define BBF_11 3
|
||||
/* sandisk 15nm flash prog first page without data and check status */
|
||||
#define BBF_3 4
|
||||
|
||||
#define MPM_0 0 /* block 0 ~ 1 */
|
||||
#define MPM_1 1 /* block 0 ~ 2048... */
|
||||
|
||||
struct NAND_PARA_INFO_T {
|
||||
u8 id_bytes;
|
||||
u8 nand_id[6];
|
||||
u8 vendor;
|
||||
u8 die_per_chip;
|
||||
u8 sec_per_page;
|
||||
u16 page_per_blk;
|
||||
u8 cell; /* 1 slc , 2 mlc , 3 tlc */
|
||||
u8 plane_per_die;
|
||||
u16 blk_per_plane;
|
||||
u16 operation_opt;
|
||||
u8 lsb_mode;
|
||||
u8 read_retry_mode;
|
||||
u8 ecc_bits;
|
||||
u8 access_freq;
|
||||
u8 opt_mode;
|
||||
u8 die_gap;
|
||||
u8 bad_block_mode;
|
||||
u8 multi_plane_mode;
|
||||
u8 reversd2[6]; /* 32 bytes */
|
||||
};
|
||||
|
||||
struct FLASH_INFO_T {
|
||||
u16 block_size;
|
||||
u8 ecc_bits;
|
||||
u32 flash_size;
|
||||
u16 page_size;
|
||||
u8 access_time;
|
||||
u8 manufacturer_name;
|
||||
u8 flash_mask;
|
||||
};
|
||||
|
||||
extern struct nand_phy_info g_nand_phy_info;
|
||||
extern struct nand_ops g_nand_ops;
|
||||
extern void __iomem *nandc_base;
|
||||
|
||||
void nandc_flash_get_id(u8 cs, void *buf);
|
||||
void nandc_flash_reset(u8 chip_sel);
|
||||
u32 nandc_flash_init(void __iomem *nandc_addr);
|
||||
u32 nandc_flash_deinit(void);
|
||||
|
||||
#endif
|
||||
70
drivers/rkflash/flash_com.h
Normal file
70
drivers/rkflash/flash_com.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __FLASH_COM_H
|
||||
#define __FLASH_COM_H
|
||||
|
||||
#include "typedef.h"
|
||||
|
||||
#define NAND_ERROR INVALID_UINT32
|
||||
#define NAND_OK 0
|
||||
|
||||
#define NAND_STS_OK 0 /* bit 0 ecc error or ok */
|
||||
#define NAND_STS_REFRESH 256 /* need refresh */
|
||||
#define NAND_STS_EMPTY 512 /* page is not proged */
|
||||
#define NAND_STS_ECC_ERR NAND_ERROR
|
||||
|
||||
#define FULL_SLC 0
|
||||
#define SLC 1
|
||||
|
||||
#define NAND_FLASH_MLC_PAGE_TAG 0xFFFF
|
||||
#define MAX_FLASH_PAGE_SIZE 0x1000 /* 4KB */
|
||||
|
||||
#define PAGE_ADDR_BITS 0
|
||||
#define PAGE_ADDR_MASK ((1u << 11) - 1)
|
||||
#define BLOCK_ADDR_BITS 11
|
||||
#define BLOCK_ADDR_MASK ((1u << 14) - 1)
|
||||
#define DIE_ADDR_BITS 25
|
||||
#define DIE_ADDR_MASK ((1u << 3) - 1)
|
||||
#define FLAG_ADDR_BITS 28
|
||||
#define FLAG_ADDR_MASK ((1u << 4) - 1)
|
||||
#define PHY_BLK_DIE_ADDR_BITS 14
|
||||
|
||||
struct nand_req {
|
||||
u32 status;
|
||||
u32 page_addr; /* 31:28 flag, 27:25: die, 24:11 block, 10:0 page */
|
||||
u32 *p_data;
|
||||
u32 *p_spare;
|
||||
u32 lpa;
|
||||
};
|
||||
|
||||
struct nand_phy_info {
|
||||
u16 nand_type; /* SLC,MLC,TLC */
|
||||
u16 die_num; /* number of LUNs */
|
||||
u16 plane_per_die;
|
||||
u16 blk_per_plane;
|
||||
u16 blk_per_die;
|
||||
u16 page_per_blk; /* in MLC mode */
|
||||
u16 page_per_slc_blk; /* in SLC mode */
|
||||
u16 sec_per_page; /* physical page data size */
|
||||
u16 sec_per_blk; /* physical page data size */
|
||||
u16 byte_per_sec; /* size of logical sectors */
|
||||
u16 reserved_blk; /* reserved for boot loader in die 0*/
|
||||
u8 ecc_bits;
|
||||
};
|
||||
|
||||
struct nand_ops {
|
||||
s32 (*get_bad_blk_list)(u16 *table, u32 die);
|
||||
u32 (*erase_blk)(u8 cs, u32 page_addr);
|
||||
u32 (*prog_page)(u8 cs, u32 page_addr, u32 *data, u32 *spare);
|
||||
u32 (*read_page)(u8 cs, u32 page_addr, u32 *data, u32 *spare);
|
||||
};
|
||||
|
||||
s32 ftl_flash_prog_pages(void *req, u32 num_req, u32 flash_type, u32 check);
|
||||
s32 ftl_flash_read_pages(void *req, u32 num_req, u32 flash_type);
|
||||
s32 ftl_flash_erase_blocks(void *req, u32 num_req);
|
||||
s32 ftl_flash_test_blk(u16 phy_block);
|
||||
s32 ftl_flash_get_bad_blk_list(u16 *table, u32 die);
|
||||
|
||||
#endif
|
||||
120
drivers/rkflash/ftl_flash_plat.c
Normal file
120
drivers/rkflash/ftl_flash_plat.c
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include "flash_com.h"
|
||||
|
||||
struct nand_phy_info g_nand_phy_info;
|
||||
struct nand_ops g_nand_ops;
|
||||
|
||||
static u32 check_buf[MAX_FLASH_PAGE_SIZE / 4];
|
||||
static u32 check_spare_buf[MAX_FLASH_PAGE_SIZE / 8 / 4];
|
||||
static u32 pg_buf0[MAX_FLASH_PAGE_SIZE / 4];
|
||||
|
||||
static u32 l2p_addr_tran(struct nand_req *req, u32 *addr, u32 *p_die)
|
||||
{
|
||||
u16 block_index, page_index;
|
||||
u16 blk_per_die = g_nand_phy_info.blk_per_die;
|
||||
u32 die_index;
|
||||
|
||||
block_index = (u16)((req[0].page_addr >> BLOCK_ADDR_BITS) &
|
||||
BLOCK_ADDR_MASK);
|
||||
page_index = (u16)(req[0].page_addr & PAGE_ADDR_MASK);
|
||||
die_index = (u16)((req[0].page_addr >> DIE_ADDR_BITS) &
|
||||
DIE_ADDR_MASK);
|
||||
*p_die = die_index;
|
||||
*addr = (block_index + blk_per_die * die_index) *
|
||||
g_nand_phy_info.page_per_blk + page_index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 ftl_flash_prog_pages(void *request, u32 num_req, u32 flash_type, u32 check)
|
||||
{
|
||||
u32 i, cs, status, addr;
|
||||
struct nand_req *req = (struct nand_req *)request;
|
||||
|
||||
for (i = 0; i < num_req; i++) {
|
||||
l2p_addr_tran(&req[i], &addr, &cs);
|
||||
status = g_nand_ops.prog_page(cs,
|
||||
addr,
|
||||
req[i].p_data,
|
||||
req[i].p_spare);
|
||||
req[i].status = status;
|
||||
if (status != NAND_STS_OK)
|
||||
req[i].status = NAND_STS_ECC_ERR;
|
||||
}
|
||||
|
||||
if (check == 0)
|
||||
return 0;
|
||||
for (i = 0; i < num_req; i++) {
|
||||
l2p_addr_tran(&req[i], &addr, &cs);
|
||||
status = g_nand_ops.read_page(cs,
|
||||
addr,
|
||||
check_buf,
|
||||
check_spare_buf);
|
||||
if (status != NAND_STS_ECC_ERR)
|
||||
req[i].status = NAND_STS_OK;
|
||||
if (check_buf[0] != req[i].p_data[0])
|
||||
req[i].status = NAND_STS_ECC_ERR;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 ftl_flash_read_pages(void *request, u32 num_req, u32 flash_type)
|
||||
{
|
||||
u32 i, cs, status, addr;
|
||||
struct nand_req *req = (struct nand_req *)request;
|
||||
|
||||
for (i = 0; i < num_req; i++) {
|
||||
l2p_addr_tran(&req[i], &addr, &cs);
|
||||
status = g_nand_ops.read_page(cs,
|
||||
addr,
|
||||
req[i].p_data,
|
||||
req[i].p_spare);
|
||||
req[i].status = status;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
s32 ftl_flash_erase_blocks(void *request, u32 num_req)
|
||||
{
|
||||
u32 i, cs, status, addr;
|
||||
struct nand_req *req = (struct nand_req *)request;
|
||||
|
||||
for (i = 0; i < num_req; i++) {
|
||||
l2p_addr_tran(&req[i], &addr, &cs);
|
||||
status = g_nand_ops.erase_blk(cs, addr);
|
||||
req[i].status = status;
|
||||
if (status != NAND_STS_OK)
|
||||
req[i].status = NAND_STS_ECC_ERR;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
s32 ftl_flash_get_bad_blk_list(u16 *table, u32 die)
|
||||
{
|
||||
return g_nand_ops.get_bad_blk_list(table, die);
|
||||
}
|
||||
|
||||
s32 ftl_flash_test_blk(u16 phy_block)
|
||||
{
|
||||
s32 sts = 0;
|
||||
u32 spare[16];
|
||||
struct nand_req req;
|
||||
|
||||
req.p_data = pg_buf0;
|
||||
req.p_spare = spare;
|
||||
memset(spare, 0xA5, 32);
|
||||
memset(pg_buf0, 0x5A, 8);
|
||||
req.page_addr = phy_block << BLOCK_ADDR_BITS;
|
||||
ftl_flash_erase_blocks((void *)&req, 1);
|
||||
ftl_flash_prog_pages((void *)&req, 1, SLC, 1);
|
||||
if (req.status == NAND_STS_ECC_ERR) {
|
||||
PRINT_E("%s %x is bad block\n", __func__, phy_block);
|
||||
sts = -1;
|
||||
}
|
||||
ftl_flash_erase_blocks((void *)&req, 1);
|
||||
|
||||
return sts;
|
||||
}
|
||||
51
drivers/rkflash/ftl_include.h
Normal file
51
drivers/rkflash/ftl_include.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _FTL_INCLUDE_
|
||||
#define _FTL_INCLUDE_
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "flash_com.h"
|
||||
#include "typedef.h"
|
||||
|
||||
#define ENABLE_LOW_FORMAT
|
||||
#define SYS_FTL_VERSION "ftl_ver 1.2.2"
|
||||
|
||||
/*
|
||||
* debug
|
||||
*/
|
||||
#define FTL_DEBUG_LEVEL D_INF
|
||||
#define FTL_DEBUG(level, format, arg...) \
|
||||
do {\
|
||||
if ((level) <= FTL_DEBUG_LEVEL) {\
|
||||
pr_info(format, ##arg);\
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define D_ERR 0
|
||||
#define D_WAN 1
|
||||
#define D_INF 2
|
||||
#define D_DBG 3
|
||||
|
||||
/* For init, load, recovery, flush_all */
|
||||
#define FTL_DBG_GLB D_DBG
|
||||
/* For open_blk, erase_blk, write_trace_page, get_trace_list */
|
||||
#define FTL_DBG_BLK (FTL_DBG_GLB + 1)
|
||||
/* For flush 1 cache, write page */
|
||||
#define FTL_DBG_PAGE (FTL_DBG_GLB + 2)
|
||||
/* For lookup/update l2p */
|
||||
#define FTL_DBG_MAP (FTL_DBG_GLB + 3)
|
||||
|
||||
#define FTL_DEBUG_BREAK(exp) \
|
||||
do { \
|
||||
if (exp) { \
|
||||
FTL_DEBUG(0, "FILE: %s:%d:\n", __FILE__, __LINE__);\
|
||||
dump_ftl_info();\
|
||||
while (1)\
|
||||
;\
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
63
drivers/rkflash/nand_boot.c
Normal file
63
drivers/rkflash/nand_boot.c
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include "flash.h"
|
||||
#include "nand_boot.h"
|
||||
#include "rk_sftl.h"
|
||||
#include "typedef.h"
|
||||
|
||||
int sftl_flash_init(void __iomem *reg_addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = nandc_flash_init(reg_addr);
|
||||
if (ret == 0)
|
||||
ret = sftl_init();
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sftl_flash_init);
|
||||
|
||||
void sftl_flash_read_id(u8 chip_sel, void *buf)
|
||||
{
|
||||
nandc_flash_get_id(chip_sel, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sftl_flash_read_id);
|
||||
|
||||
unsigned int sftl_flash_get_capacity(void)
|
||||
{
|
||||
return sftl_get_density();
|
||||
}
|
||||
|
||||
int sftl_flash_write(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
sftl_write(sec, n_sec, p_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sftl_flash_read(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
sftl_read(sec, n_sec, p_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sftl_flash_deinit(void)
|
||||
{
|
||||
u8 chip_sel = 0;
|
||||
|
||||
sftl_deinit();
|
||||
nandc_flash_reset(chip_sel);
|
||||
}
|
||||
|
||||
int sftl_flash_resume(void __iomem *reg_addr)
|
||||
{
|
||||
return nandc_flash_init(reg_addr);
|
||||
}
|
||||
|
||||
void sftl_flash_clean_irq(void)
|
||||
{
|
||||
}
|
||||
17
drivers/rkflash/nand_boot.h
Normal file
17
drivers/rkflash/nand_boot.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _SFC_H
|
||||
#define _SFC_H
|
||||
|
||||
int sftl_flash_init(void __iomem *reg_addr);
|
||||
void sftl_flash_read_id(u8 chip_sel, void *buf);
|
||||
int sftl_flash_read(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
int sftl_flash_write(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
unsigned int sftl_flash_get_capacity(void);
|
||||
void sftl_flash_deinit(void);
|
||||
int sftl_flash_resume(void __iomem *reg_addr);
|
||||
void sftl_flash_clean_irq(void);
|
||||
|
||||
#endif
|
||||
294
drivers/rkflash/nandc.c
Normal file
294
drivers/rkflash/nandc.c
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "flash.h"
|
||||
#include "flash_com.h"
|
||||
#include "nandc.h"
|
||||
#include "typedef.h"
|
||||
|
||||
#define CPU_DELAY_NS(n) ndelay(n)
|
||||
|
||||
#define NANDC_MASTER_EN
|
||||
|
||||
void __iomem *nandc_base;
|
||||
|
||||
static u32 g_nandc_ecc_bits;
|
||||
#ifdef NANDC_MASTER_EN
|
||||
static struct MASTER_INFO_T master;
|
||||
static u32 *g_master_temp_buf;
|
||||
#endif
|
||||
|
||||
void nandc_init(void __iomem *nandc_addr)
|
||||
{
|
||||
union FM_CTL_T ctl_reg;
|
||||
|
||||
nandc_base = nandc_addr;
|
||||
|
||||
ctl_reg.d32 = 0;
|
||||
ctl_reg.V6.wp = 1;
|
||||
nandc_writel(ctl_reg.d32, NANDC_FMCTL);
|
||||
nandc_writel(0, NANDC_RANDMZ_CFG);
|
||||
nandc_time_cfg(40);
|
||||
|
||||
#ifdef NANDC_MASTER_EN
|
||||
if (!g_master_temp_buf)
|
||||
g_master_temp_buf = (u32 *)ftl_malloc(MAX_FLASH_PAGE_SIZE +
|
||||
MAX_FLASH_PAGE_SIZE / 8);
|
||||
master.page_buf = &g_master_temp_buf[0];
|
||||
master.spare_buf = &g_master_temp_buf[MAX_FLASH_PAGE_SIZE / 4];
|
||||
master.mapped = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void nandc_flash_cs(u8 chip_sel)
|
||||
{
|
||||
union FM_CTL_T tmp;
|
||||
|
||||
tmp.d32 = nandc_readl(NANDC_FMCTL);
|
||||
tmp.V6.cs = 0x01 << chip_sel;
|
||||
nandc_writel(tmp.d32, NANDC_FMCTL);
|
||||
}
|
||||
|
||||
void nandc_flash_de_cs(u8 chip_sel)
|
||||
{
|
||||
union FM_CTL_T tmp;
|
||||
|
||||
tmp.d32 = nandc_readl(NANDC_FMCTL);
|
||||
tmp.V6.cs = 0;
|
||||
tmp.V6.flash_abort_clear = 0;
|
||||
nandc_writel(tmp.d32, NANDC_FMCTL);
|
||||
}
|
||||
|
||||
u32 nandc_delayns(u32 count)
|
||||
{
|
||||
CPU_DELAY_NS(count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nandc_wait_flash_ready(u8 chip_sel)
|
||||
{
|
||||
union FM_CTL_T tmp;
|
||||
u32 status;
|
||||
u32 i;
|
||||
|
||||
status = 0;
|
||||
for (i = 0; i < 100000; i++) {
|
||||
nandc_delayns(100);
|
||||
tmp.d32 = nandc_readl(NANDC_FMCTL);
|
||||
if (tmp.V6.rdy != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= 100000)
|
||||
status = -1;
|
||||
return status;
|
||||
}
|
||||
|
||||
void nandc_randmz_sel(u8 chip_sel, u32 randmz_seed)
|
||||
{
|
||||
nandc_writel(randmz_seed, NANDC_RANDMZ_CFG);
|
||||
}
|
||||
|
||||
void nandc_time_cfg(u32 ns)
|
||||
{
|
||||
if (ns < 36)
|
||||
nandc_writel(0x1061, NANDC_FMWAIT);
|
||||
else if (ns >= 100)
|
||||
nandc_writel(0x2082, NANDC_FMWAIT);
|
||||
else
|
||||
nandc_writel(0x1081, NANDC_FMWAIT);
|
||||
}
|
||||
|
||||
void nandc_bch_sel(u8 bits)
|
||||
{
|
||||
union BCH_CTL_T tmp;
|
||||
union FL_CTL_T fl_reg;
|
||||
|
||||
fl_reg.d32 = 0;
|
||||
fl_reg.V6.rst = 1;
|
||||
nandc_writel(fl_reg.d32, NANDC_FLCTL);
|
||||
g_nandc_ecc_bits = bits;
|
||||
tmp.d32 = 0;
|
||||
tmp.V6.addr = 0x10;
|
||||
tmp.V6.bch_mode1 = 0;
|
||||
if (bits == 16) {
|
||||
tmp.V6.bch_mode = 0;
|
||||
} else if (bits == 24) {
|
||||
tmp.V6.bch_mode = 1;
|
||||
} else {
|
||||
tmp.V6.bch_mode1 = 1;
|
||||
tmp.V6.bch_mode = 1;
|
||||
if (bits == 40)
|
||||
tmp.V6.bch_mode = 0;
|
||||
}
|
||||
tmp.V6.rst = 1;
|
||||
nandc_writel(tmp.d32, NANDC_BCHCTL);
|
||||
}
|
||||
|
||||
static void nandc_xfer_start(u8 chip_sel,
|
||||
u8 dir,
|
||||
u8 sector_count,
|
||||
u8 st_buf,
|
||||
u32 *p_data,
|
||||
u32 *p_spare)
|
||||
{
|
||||
union BCH_CTL_T bch_reg;
|
||||
union FL_CTL_T fl_reg;
|
||||
u8 bus_mode = (p_spare || p_data);
|
||||
u32 i;
|
||||
union MTRANS_CFG_T master_reg;
|
||||
u16 *p_spare_tmp = (u16 *)p_spare;
|
||||
|
||||
fl_reg.d32 = 0;
|
||||
bch_reg.d32 = nandc_readl(NANDC_BCHCTL);
|
||||
bch_reg.V6.addr = 0x10;
|
||||
bch_reg.V6.power_down = 0;
|
||||
bch_reg.V6.region = chip_sel;
|
||||
|
||||
fl_reg.V6.rdn = dir;
|
||||
fl_reg.V6.dma = 1;
|
||||
fl_reg.V6.tr_count = 1;
|
||||
fl_reg.V6.async_tog_mix = 1;
|
||||
fl_reg.V6.cor_en = 1;
|
||||
fl_reg.V6.st_addr = st_buf / 2;
|
||||
|
||||
master_reg.d32 = nandc_readl(NANDC_MTRANS_CFG);
|
||||
master_reg.V6.bus_mode = 0;
|
||||
#ifdef NANDC_MASTER_EN
|
||||
if (bus_mode != 0 && dir != 0) {
|
||||
u32 spare_sz = 64;
|
||||
|
||||
for (i = 0; i < sector_count / 2; i++) {
|
||||
if (p_spare) {
|
||||
master.spare_buf[i * spare_sz / 4] =
|
||||
(p_spare_tmp[0]) | ((u32)p_spare_tmp[1] << 16);
|
||||
p_spare_tmp += 2;
|
||||
} else{
|
||||
master.spare_buf[i * spare_sz / 4] =
|
||||
0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
fl_reg.V6.page_num = (sector_count + 1) / 2;
|
||||
master.page_vir = (u32 *)((p_data == (u32 *)NULL) ?
|
||||
master.page_buf :
|
||||
(u32 *)p_data);
|
||||
master.spare_vir = (u32 *)master.spare_buf;
|
||||
master.page_phy =
|
||||
(u32)rknandc_dma_map_single((unsigned long)master.page_vir,
|
||||
fl_reg.V6.page_num * 1024,
|
||||
dir);
|
||||
master.spare_phy =
|
||||
(u32)rknandc_dma_map_single((unsigned long)master.spare_vir,
|
||||
fl_reg.V6.page_num * 64,
|
||||
dir);
|
||||
master.mapped = 1;
|
||||
nandc_writel(master.page_phy, NANDC_MTRANS_SADDR0);
|
||||
nandc_writel(master.spare_phy, NANDC_MTRANS_SADDR1);
|
||||
master_reg.d32 = 0;
|
||||
master_reg.V6.incr_num = 16;
|
||||
master_reg.V6.burst = 7;
|
||||
if ((((unsigned long)p_data) & 0x03) == 0)
|
||||
master_reg.V6.hsize = 2;
|
||||
master_reg.V6.bus_mode = 1;
|
||||
master_reg.V6.ahb_wr = !dir;
|
||||
master_reg.V6.ahb_wr_st = 1;
|
||||
#endif
|
||||
|
||||
nandc_writel(master_reg.d32, NANDC_MTRANS_CFG);
|
||||
nandc_writel(bch_reg.d32, NANDC_BCHCTL);
|
||||
nandc_writel(fl_reg.d32, NANDC_FLCTL);
|
||||
fl_reg.V6.start = 1;
|
||||
nandc_writel(fl_reg.d32, NANDC_FLCTL);
|
||||
}
|
||||
|
||||
static void nandc_xfer_comp(u8 chip_sel)
|
||||
{
|
||||
union FL_CTL_T fl_reg;
|
||||
union MTRANS_CFG_T master_reg;
|
||||
|
||||
master_reg.d32 = nandc_readl(NANDC_MTRANS_CFG);
|
||||
if (master_reg.V6.bus_mode != 0) {
|
||||
union MTRANS_STAT_T stat_reg;
|
||||
|
||||
if (master_reg.V6.ahb_wr != 0) {
|
||||
do {
|
||||
fl_reg.d32 = nandc_readl(NANDC_FLCTL);
|
||||
stat_reg.d32 = nandc_readl(NANDC_MTRANS_STAT);
|
||||
} while (stat_reg.V6.mtrans_cnt < fl_reg.V6.page_num);
|
||||
|
||||
if (master.mapped) {
|
||||
rknandc_dma_unmap_single((u64)master.page_phy,
|
||||
fl_reg.V6.page_num * 1024,
|
||||
0);
|
||||
rknandc_dma_unmap_single((u64)master.spare_phy,
|
||||
fl_reg.V6.page_num * 64,
|
||||
0);
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
fl_reg.d32 = nandc_readl(NANDC_FLCTL);
|
||||
} while (fl_reg.V6.tr_rdy == 0);
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
fl_reg.d32 = nandc_readl(NANDC_FLCTL);
|
||||
} while ((fl_reg.V6.tr_rdy == 0));
|
||||
}
|
||||
}
|
||||
|
||||
u32 nandc_xfer_data(u8 chip_sel, u8 dir, u8 sector_count,
|
||||
u32 *p_data, u32 *p_spare)
|
||||
{
|
||||
u32 status = NAND_STS_OK;
|
||||
u32 i;
|
||||
u32 spare[16];
|
||||
union BCH_ST_T bch_st_reg;
|
||||
|
||||
if (dir == NANDC_WRITE && !p_spare) {
|
||||
p_spare = (u32 *)spare;
|
||||
memset(spare, 0xFF, sizeof(spare));
|
||||
}
|
||||
nandc_xfer_start(chip_sel, dir, sector_count, 0, p_data, p_spare);
|
||||
nandc_xfer_comp(chip_sel);
|
||||
if (dir == NANDC_READ) {
|
||||
if (p_spare) {
|
||||
u32 spare_sz = 64;
|
||||
u32 temp_data;
|
||||
u8 *p_spare_temp = (u8 *)p_spare;
|
||||
|
||||
for (i = 0; i < sector_count / 2; i++) {
|
||||
temp_data = master.spare_buf[i * spare_sz / 4];
|
||||
*p_spare_temp++ = (u8)temp_data;
|
||||
*p_spare_temp++ = (u8)(temp_data >> 8);
|
||||
*p_spare_temp++ = (u8)(temp_data >> 16);
|
||||
*p_spare_temp++ = (u8)(temp_data >> 24);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < sector_count / 4 ; i++) {
|
||||
bch_st_reg.d32 = nandc_readl(NANDC_BCHST(i));
|
||||
if (bch_st_reg.V6.fail0 || bch_st_reg.V6.fail1) {
|
||||
status = NAND_STS_ECC_ERR;
|
||||
} else {
|
||||
u32 tmp = 0;
|
||||
|
||||
tmp =
|
||||
max(bch_st_reg.V6.err_bits0 |
|
||||
((u32)bch_st_reg.V6.err_bits0_5 << 5),
|
||||
bch_st_reg.V6.err_bits1 |
|
||||
((u32)bch_st_reg.V6.err_bits1_5 << 5));
|
||||
status = max(tmp, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
nandc_writel(0, NANDC_MTRANS_CFG);
|
||||
return status;
|
||||
}
|
||||
|
||||
void nandc_clean_irq(void)
|
||||
{
|
||||
}
|
||||
222
drivers/rkflash/nandc.h
Normal file
222
drivers/rkflash/nandc.h
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __NAND_H
|
||||
#define __NAND_H
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
#define nandc_writel(v, offs) writel((v), (offs) + nandc_base)
|
||||
#define nandc_readl(offs) readl((offs) + nandc_base)
|
||||
|
||||
#define NANDC_READ 0
|
||||
#define NANDC_WRITE 1
|
||||
|
||||
/* INT ID */
|
||||
enum NANDC_IRQ_NUM_T {
|
||||
NC_IRQ_DMA = 0,
|
||||
NC_IRQ_FRDY,
|
||||
NC_IRQ_BCHERR,
|
||||
NC_IRQ_BCHFAIL,
|
||||
NC_IRQ_LLP
|
||||
};
|
||||
|
||||
union FM_CTL_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned cs : 8; /* bits[0:7] */
|
||||
unsigned wp : 1; /* bits[8] */
|
||||
unsigned rdy : 1; /* bits[9] */
|
||||
unsigned fifo_empty : 1; /* bits[10] */
|
||||
unsigned reserved11 : 1; /* bits[11] */
|
||||
unsigned dwidth : 1; /* bits[12] */
|
||||
unsigned tm : 1; /* bits[13] */
|
||||
unsigned onficlk_en : 1; /* bits[14] */
|
||||
unsigned toggle_en : 1; /* bits[15] */
|
||||
unsigned flash_abort_en : 1; /* bits[16] */
|
||||
unsigned flash_abort_clear : 1; /* bits[17] */
|
||||
unsigned reserved18_23 : 6; /* bits[18:23] */
|
||||
unsigned read_delay : 3; /* bits[24:26] */
|
||||
unsigned reserved27_31 : 5; /* bits[27:31] */
|
||||
} V6;
|
||||
};
|
||||
|
||||
union FM_WAIT_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned csrw : 5;
|
||||
unsigned rwpw : 6;
|
||||
unsigned rdy : 1;
|
||||
unsigned rwcs : 6;
|
||||
unsigned reserved18_23 : 6;
|
||||
unsigned fmw_dly : 6;
|
||||
unsigned fmw_dly_en : 1;
|
||||
unsigned reserved31_31 : 1;
|
||||
} V6;
|
||||
};
|
||||
|
||||
union FL_CTL_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned rst : 1;
|
||||
unsigned rdn : 1;
|
||||
unsigned start : 1;
|
||||
unsigned dma : 1;
|
||||
unsigned st_addr : 1;
|
||||
unsigned tr_count : 2;
|
||||
unsigned rdy_ignore : 1;
|
||||
/* unsigned int_clr : 1; */
|
||||
/* unsigned int_en : 1; */
|
||||
unsigned reserved8_9 : 2;
|
||||
unsigned cor_en : 1;
|
||||
unsigned lba_en : 1;
|
||||
unsigned spare_size : 7;
|
||||
unsigned reserved19 : 1;
|
||||
unsigned tr_rdy : 1;
|
||||
unsigned page_size : 1;
|
||||
unsigned page_num : 6;
|
||||
unsigned low_power : 1;
|
||||
unsigned async_tog_mix : 1;
|
||||
unsigned reserved30_31 : 2;
|
||||
} V6;
|
||||
};
|
||||
|
||||
union BCH_CTL_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned rst : 1;
|
||||
unsigned reserved : 1;
|
||||
unsigned addr_not_care : 1;
|
||||
unsigned power_down : 1;
|
||||
unsigned bch_mode : 1; /* 0-16bit/1KB, 1-24bit/1KB */
|
||||
unsigned region : 3;
|
||||
unsigned addr : 8;
|
||||
unsigned bchpage : 1;
|
||||
unsigned reserved17 : 1;
|
||||
unsigned bch_mode1 : 1;
|
||||
unsigned thres : 8;
|
||||
unsigned reserved27_31 : 5;
|
||||
} V6;
|
||||
};
|
||||
|
||||
union BCH_ST_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned errf0 : 1;
|
||||
unsigned done0 : 1;
|
||||
unsigned fail0 : 1;
|
||||
unsigned err_bits0 : 5;
|
||||
unsigned err_bits_low0 : 5;
|
||||
unsigned errf1 : 1;
|
||||
unsigned done1 : 1;
|
||||
unsigned fail1 : 1;
|
||||
unsigned err_bits1 : 5;
|
||||
unsigned err_bits_low1 : 5;
|
||||
unsigned rdy : 1;
|
||||
/* unsigned cnt : 1; */
|
||||
unsigned err_bits0_5 : 1;
|
||||
unsigned err_bits_low0_5 : 1;
|
||||
unsigned err_bits1_5 : 1;
|
||||
unsigned err_bits_low1_5 : 1;
|
||||
unsigned reserved31_31 : 1;
|
||||
} V6;
|
||||
};
|
||||
|
||||
union MTRANS_CFG_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned ahb_wr_st : 1;
|
||||
unsigned ahb_wr : 1;
|
||||
unsigned bus_mode : 1;
|
||||
unsigned hsize : 3;
|
||||
unsigned burst : 3;
|
||||
unsigned incr_num : 5;
|
||||
unsigned fl_pwd : 1;
|
||||
unsigned ahb_rst : 1;
|
||||
unsigned reserved16_31 : 16;
|
||||
} V6;
|
||||
};
|
||||
|
||||
union MTRANS_STAT_T {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned bus_err : 16;
|
||||
unsigned mtrans_cnt : 5;
|
||||
unsigned reserved21_31 : 11;
|
||||
} V6;
|
||||
};
|
||||
|
||||
/* NANDC Registers */
|
||||
#define NANDC_FMCTL 0x0
|
||||
#define NANDC_FMWAIT 0x4
|
||||
#define NANDC_FLCTL 0x8
|
||||
#define NANDC_BCHCTL 0xc
|
||||
#define NANDC_MTRANS_CFG 0x10
|
||||
#define NANDC_MTRANS_SADDR0 0x14
|
||||
#define NANDC_MTRANS_SADDR1 0x18
|
||||
#define NANDC_MTRANS_STAT 0x1c
|
||||
#define NANDC_DLL_CTL_REG0 0x130
|
||||
#define NANDC_DLL_CTL_REG1 0x134
|
||||
#define NANDC_DLL_OBS_REG0 0x138
|
||||
#define NANDC_RANDMZ_CFG 0x150
|
||||
#define NANDC_EBI_EN 0x154
|
||||
#define NANDC_FMWAIT_SYN 0x158
|
||||
#define NANDC_MTRANS_STAT2 0x15c
|
||||
#define NANDC_NANDC_VER 0x160
|
||||
#define NANDC_LLP_CTL 0x164
|
||||
#define NANDC_LLP_STAT 0x168
|
||||
#define NANDC_INTEN 0x16c
|
||||
#define NANDC_INTCLR 0x170
|
||||
#define NANDC_INTST 0x174
|
||||
#define NANDC_SPARE0 0x200
|
||||
#define NANDC_SPARE1 0x230
|
||||
|
||||
#define NANDC_BCHST(i) ({ \
|
||||
u32 x = (i); \
|
||||
4 * x + x < 8 ? 0x20 : 0x520; })
|
||||
|
||||
#define NANDC_CHIP_DATA(id) (0x800 + (id) * 0x100)
|
||||
#define NANDC_CHIP_ADDR(id) (0x800 + (id) * 0x100 + 0x4)
|
||||
#define NANDC_CHIP_CMD(id) (0x800 + (id) * 0x100 + 0x8)
|
||||
|
||||
struct MASTER_INFO_T {
|
||||
u32 *page_buf; /* [DATA_LEN]; */
|
||||
u32 *spare_buf; /* [DATA_LEN / (1024/128)]; */
|
||||
u32 *page_vir; /* page_buf_vir_addr */
|
||||
u32 *spare_vir; /* spare_buf_vir_addr */
|
||||
u32 page_phy; /* page_buf_phy_addr */
|
||||
u32 spare_phy; /* spare_buf_phy_addr*/
|
||||
u32 mapped;
|
||||
u32 cnt;
|
||||
};
|
||||
|
||||
struct CHIP_MAP_INFO_T {
|
||||
u32 *nandc_addr;
|
||||
u32 chip_num;
|
||||
};
|
||||
|
||||
unsigned long rknandc_dma_map_single(unsigned long ptr,
|
||||
int size,
|
||||
int dir);
|
||||
void rknandc_dma_unmap_single(unsigned long ptr,
|
||||
int size,
|
||||
int dir);
|
||||
|
||||
void nandc_init(void __iomem *nandc_addr);
|
||||
void nandc_flash_cs(u8 chip_sel);
|
||||
void nandc_flash_de_cs(u8 chip_sel);
|
||||
u32 nandc_wait_flash_ready(u8 chip_sel);
|
||||
u32 nandc_delayns(u32 count);
|
||||
u32 nandc_xfer_data(u8 chip_sel,
|
||||
u8 dir,
|
||||
u8 sector_count,
|
||||
u32 *p_data,
|
||||
u32 *p_spare);
|
||||
void nandc_randmz_sel(u8 chip_sel, u32 randmz_seed);
|
||||
void nandc_bch_sel(u8 bits);
|
||||
void nandc_read_not_case_busy_en(u8 en);
|
||||
void nandc_time_cfg(u32 ns);
|
||||
void nandc_clean_irq(void);
|
||||
|
||||
#endif
|
||||
14046
drivers/rkflash/rk_sftl.S
Normal file
14046
drivers/rkflash/rk_sftl.S
Normal file
File diff suppressed because it is too large
Load Diff
16
drivers/rkflash/rk_sftl.h
Normal file
16
drivers/rkflash/rk_sftl.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __RK_SFTL_H
|
||||
#define __RK_SFTL_H
|
||||
|
||||
u32 ftl_low_format(void);
|
||||
int sftl_init(void);
|
||||
int sftl_deinit(void);
|
||||
int sftl_read(u32 index, u32 count, u8 *buf);
|
||||
int sftl_write(u32 index, u32 count, u8 *buf);
|
||||
u32 sftl_get_density(void);
|
||||
s32 sftl_gc(void);
|
||||
|
||||
#endif
|
||||
20
drivers/rkflash/rkflash_api.h
Normal file
20
drivers/rkflash/rkflash_api.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __RK_FLASH_API_H
|
||||
#define __RK_FLASH_API_H
|
||||
|
||||
#ifdef CONFIG_RK_NANDC_NAND
|
||||
#include "nand_boot.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_RK_SFC_NOR
|
||||
#include "sfc_nor_boot.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_RK_SFC_NAND
|
||||
#include "sfc_nand_boot.h"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
704
drivers/rkflash/rkflash_blk.c
Normal file
704
drivers/rkflash/rkflash_blk.c
Normal file
|
|
@ -0,0 +1,704 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/blkpg.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hdreg.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include "rkflash_api.h"
|
||||
#include "rkflash_blk.h"
|
||||
|
||||
#include "../soc/rockchip/flash_vendor_storage.h"
|
||||
|
||||
static struct flash_boot_ops nandc_nand_ops = {
|
||||
#ifdef CONFIG_RK_NANDC_NAND
|
||||
FLASH_TYPE_NANDC_NAND,
|
||||
sftl_flash_init,
|
||||
sftl_flash_read,
|
||||
sftl_flash_write,
|
||||
sftl_flash_get_capacity,
|
||||
sftl_flash_deinit,
|
||||
sftl_flash_resume,
|
||||
sftl_flash_read,
|
||||
sftl_flash_write,
|
||||
#else
|
||||
-1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct flash_boot_ops sfc_nor_ops = {
|
||||
#ifdef CONFIG_RK_SFC_NOR
|
||||
FLASH_TYPE_SFC_NOR,
|
||||
spi_flash_init,
|
||||
snor_read_lba,
|
||||
snor_write_lba,
|
||||
snor_capacity,
|
||||
snor_deinit,
|
||||
snor_resume,
|
||||
snor_vendor_read,
|
||||
snor_vendor_write,
|
||||
#else
|
||||
-1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct flash_boot_ops sfc_nand_ops = {
|
||||
#ifdef CONFIG_RK_SFC_NAND
|
||||
FLASH_TYPE_SFC_NAND,
|
||||
snand_init,
|
||||
snand_read,
|
||||
snand_write,
|
||||
snand_get_capacity,
|
||||
snand_deinit,
|
||||
snand_resume,
|
||||
snand_read,
|
||||
snand_write,
|
||||
#else
|
||||
-1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct flash_boot_ops *g_boot_ops[] = {
|
||||
&nandc_nand_ops,
|
||||
&sfc_nor_ops,
|
||||
&sfc_nand_ops,
|
||||
};
|
||||
|
||||
static int g_flash_type = -1;
|
||||
|
||||
static struct flash_part disk_array[MAX_PART_COUNT];
|
||||
static int g_max_part_num = 4;
|
||||
#define FW_HRADER_PT_NAME ("fw_header_p")
|
||||
static struct flash_part fw_header_p;
|
||||
|
||||
#define PART_READONLY 0x85
|
||||
#define PART_WRITEONLY 0x86
|
||||
#define PART_NO_ACCESS 0x87
|
||||
|
||||
static unsigned long totle_read_data;
|
||||
static unsigned long totle_write_data;
|
||||
static unsigned long totle_read_count;
|
||||
static unsigned long totle_write_count;
|
||||
static int rkflash_dev_initialised;
|
||||
|
||||
static char *mtd_read_temp_buffer;
|
||||
#define MTD_RW_SECTORS (512)
|
||||
static DEFINE_MUTEX(g_flash_ops_mutex);
|
||||
|
||||
static unsigned int rk_partition_init(struct flash_part *part)
|
||||
{
|
||||
int i, part_num = 0;
|
||||
int desity;
|
||||
struct STRUCT_PART_INFO *g_part; /* size 2KB */
|
||||
|
||||
g_part = kmalloc(sizeof(*g_part), GFP_KERNEL | GFP_DMA);
|
||||
if (!g_part)
|
||||
return 0;
|
||||
|
||||
if (g_boot_ops[g_flash_type]->read(0, 4, g_part) == 0) {
|
||||
if (g_part->hdr.ui_fw_tag == RK_PARTITION_TAG) {
|
||||
part_num = g_part->hdr.ui_part_entry_count;
|
||||
desity = g_boot_ops[g_flash_type]->get_capacity();
|
||||
for (i = 0; i < part_num; i++) {
|
||||
memcpy(part[i].name,
|
||||
g_part->part[i].sz_name,
|
||||
32);
|
||||
part[i].offset = g_part->part[i].ui_pt_off;
|
||||
part[i].size = g_part->part[i].ui_pt_sz;
|
||||
part[i].type = 0;
|
||||
if (part[i].size == UINT_MAX)
|
||||
part[i].size = desity - part[i].offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree(g_part);
|
||||
|
||||
memset(&fw_header_p, 0x0, sizeof(fw_header_p));
|
||||
memcpy(fw_header_p.name, FW_HRADER_PT_NAME, strlen(FW_HRADER_PT_NAME));
|
||||
fw_header_p.offset = 0x0;
|
||||
fw_header_p.size = 0x4;
|
||||
fw_header_p.type = 0;
|
||||
|
||||
return part_num;
|
||||
}
|
||||
|
||||
static int rkflash_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
seq_printf(m, "Totle Read %ld KB\n", totle_read_data >> 1);
|
||||
seq_printf(m, "Totle Write %ld KB\n", totle_write_data >> 1);
|
||||
seq_printf(m, "totle_write_count %ld\n", totle_write_count);
|
||||
seq_printf(m, "totle_read_count %ld\n", totle_read_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkflash_proc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, rkflash_proc_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations rkflash_proc_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rkflash_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int rkflash_create_procfs(void)
|
||||
{
|
||||
struct proc_dir_entry *ent;
|
||||
|
||||
ent = proc_create_data("rkflash", 0x664, NULL, &rkflash_proc_fops,
|
||||
(void *)0);
|
||||
if (!ent)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkflash_xfer(struct flash_blk_dev *dev,
|
||||
unsigned long start,
|
||||
unsigned long nsector,
|
||||
char *buf,
|
||||
int cmd,
|
||||
int totle_nsec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (dev->disable_access ||
|
||||
(cmd == WRITE && dev->readonly) ||
|
||||
(cmd == READ && dev->writeonly)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
start += dev->off_size;
|
||||
|
||||
switch (cmd) {
|
||||
case READ:
|
||||
totle_read_data += nsector;
|
||||
totle_read_count++;
|
||||
mutex_lock(&g_flash_ops_mutex);
|
||||
ret = g_boot_ops[g_flash_type]->read(start, nsector, buf);
|
||||
mutex_unlock(&g_flash_ops_mutex);
|
||||
if (ret)
|
||||
ret = -EIO;
|
||||
break;
|
||||
|
||||
case WRITE:
|
||||
totle_write_data += nsector;
|
||||
totle_write_count++;
|
||||
mutex_lock(&g_flash_ops_mutex);
|
||||
ret = g_boot_ops[g_flash_type]->write(start, nsector, buf);
|
||||
mutex_unlock(&g_flash_ops_mutex);
|
||||
if (ret)
|
||||
ret = -EIO;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(rkflash_thread_wait);
|
||||
static unsigned long rkflash_req_jiffies;
|
||||
static unsigned int rknand_req_do;
|
||||
|
||||
static int req_check_buffer_align(struct request *req, char **pbuf)
|
||||
{
|
||||
int nr_vec = 0;
|
||||
struct bio_vec bvec;
|
||||
struct req_iterator iter;
|
||||
char *buffer;
|
||||
void *firstbuf = 0;
|
||||
char *nextbuffer = 0;
|
||||
|
||||
rq_for_each_segment(bvec, req, iter) {
|
||||
buffer = page_address(bvec.bv_page) + bvec.bv_offset;
|
||||
if (!firstbuf)
|
||||
firstbuf = buffer;
|
||||
nr_vec++;
|
||||
if (nextbuffer && nextbuffer != buffer)
|
||||
return 0;
|
||||
nextbuffer = buffer + bvec.bv_len;
|
||||
}
|
||||
*pbuf = firstbuf;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rkflash_blktrans_thread(void *arg)
|
||||
{
|
||||
struct flash_blk_ops *blk_ops = arg;
|
||||
struct request_queue *rq = blk_ops->rq;
|
||||
struct request *req = NULL;
|
||||
char *buf;
|
||||
struct req_iterator rq_iter;
|
||||
struct bio_vec bvec;
|
||||
unsigned long long sector_index = ULLONG_MAX;
|
||||
unsigned long totle_nsect;
|
||||
unsigned long rq_len = 0;
|
||||
int rw_flag = 0;
|
||||
|
||||
spin_lock_irq(rq->queue_lock);
|
||||
while (!blk_ops->quit) {
|
||||
int res;
|
||||
struct flash_blk_dev *dev;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
if (!req)
|
||||
req = blk_fetch_request(rq);
|
||||
if (!req) {
|
||||
add_wait_queue(&blk_ops->thread_wq, &wait);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
spin_unlock_irq(rq->queue_lock);
|
||||
rkflash_req_jiffies = HZ / 10;
|
||||
wait_event_timeout(blk_ops->thread_wq,
|
||||
blk_ops->quit || rknand_req_do,
|
||||
rkflash_req_jiffies);
|
||||
rknand_req_do = 0;
|
||||
spin_lock_irq(rq->queue_lock);
|
||||
remove_wait_queue(&blk_ops->thread_wq, &wait);
|
||||
continue;
|
||||
} else {
|
||||
rkflash_req_jiffies = 1 * HZ;
|
||||
}
|
||||
|
||||
dev = req->rq_disk->private_data;
|
||||
totle_nsect = (req->__data_len) >> 9;
|
||||
sector_index = blk_rq_pos(req);
|
||||
rq_len = 0;
|
||||
buf = 0;
|
||||
res = 0;
|
||||
|
||||
if (req->cmd_flags & REQ_DISCARD) {
|
||||
if (!__blk_end_request_cur(req, res))
|
||||
req = NULL;
|
||||
continue;
|
||||
} else if (req->cmd_flags & REQ_FLUSH) {
|
||||
if (!__blk_end_request_cur(req, res))
|
||||
req = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
rw_flag = req->cmd_flags & REQ_WRITE;
|
||||
if (rw_flag == READ && mtd_read_temp_buffer) {
|
||||
buf = mtd_read_temp_buffer;
|
||||
req_check_buffer_align(req, &buf);
|
||||
spin_unlock_irq(rq->queue_lock);
|
||||
res = rkflash_xfer(dev,
|
||||
sector_index,
|
||||
totle_nsect,
|
||||
buf,
|
||||
rw_flag,
|
||||
totle_nsect);
|
||||
spin_lock_irq(rq->queue_lock);
|
||||
if (buf == mtd_read_temp_buffer) {
|
||||
char *p = buf;
|
||||
|
||||
rq_for_each_segment(bvec, req, rq_iter) {
|
||||
memcpy(page_address(bvec.bv_page) +
|
||||
bvec.bv_offset,
|
||||
p,
|
||||
bvec.bv_len);
|
||||
p += bvec.bv_len;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rq_for_each_segment(bvec, req, rq_iter) {
|
||||
if ((page_address(bvec.bv_page)
|
||||
+ bvec.bv_offset)
|
||||
== (buf + rq_len)) {
|
||||
rq_len += bvec.bv_len;
|
||||
} else {
|
||||
if (rq_len) {
|
||||
spin_unlock_irq(rq->queue_lock);
|
||||
res = rkflash_xfer(dev,
|
||||
sector_index,
|
||||
rq_len >> 9,
|
||||
buf,
|
||||
rw_flag,
|
||||
totle_nsect);
|
||||
spin_lock_irq(rq->queue_lock);
|
||||
}
|
||||
sector_index += rq_len >> 9;
|
||||
buf = (page_address(bvec.bv_page) +
|
||||
bvec.bv_offset);
|
||||
rq_len = bvec.bv_len;
|
||||
}
|
||||
}
|
||||
if (rq_len) {
|
||||
spin_unlock_irq(rq->queue_lock);
|
||||
res = rkflash_xfer(dev,
|
||||
sector_index,
|
||||
rq_len >> 9,
|
||||
buf,
|
||||
rw_flag,
|
||||
totle_nsect);
|
||||
spin_lock_irq(rq->queue_lock);
|
||||
}
|
||||
}
|
||||
__blk_end_request_all(req, res);
|
||||
req = NULL;
|
||||
}
|
||||
pr_info("flash th quited\n");
|
||||
blk_ops->flash_th_quited = 1;
|
||||
if (req)
|
||||
__blk_end_request_all(req, -EIO);
|
||||
while ((req = blk_fetch_request(rq)) != NULL)
|
||||
__blk_end_request_all(req, -ENODEV);
|
||||
spin_unlock_irq(rq->queue_lock);
|
||||
complete_and_exit(&blk_ops->thread_exit, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkflash_blk_request(struct request_queue *rq)
|
||||
{
|
||||
struct flash_blk_ops *blk_ops = rq->queuedata;
|
||||
struct request *req = NULL;
|
||||
|
||||
if (blk_ops->flash_th_quited) {
|
||||
while ((req = blk_fetch_request(rq)) != NULL)
|
||||
__blk_end_request_all(req, -ENODEV);
|
||||
return;
|
||||
}
|
||||
rknand_req_do = 1;
|
||||
wake_up(&blk_ops->thread_wq);
|
||||
}
|
||||
|
||||
static int rkflash_open(struct block_device *bdev, fmode_t mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkflash_release(struct gendisk *disk, fmode_t mode)
|
||||
{
|
||||
};
|
||||
|
||||
#define DISABLE_WRITE _IO('V', 0)
|
||||
#define ENABLE_WRITE _IO('V', 1)
|
||||
#define DISABLE_READ _IO('V', 2)
|
||||
#define ENABLE_READ _IO('V', 3)
|
||||
static int rkflash_ioctl(struct block_device *bdev, fmode_t mode,
|
||||
unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct flash_blk_dev *dev = bdev->bd_disk->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case ENABLE_WRITE:
|
||||
dev->disable_access = 0;
|
||||
dev->readonly = 0;
|
||||
set_disk_ro(dev->blkcore_priv, 0);
|
||||
return 0;
|
||||
|
||||
case DISABLE_WRITE:
|
||||
dev->readonly = 1;
|
||||
set_disk_ro(dev->blkcore_priv, 1);
|
||||
return 0;
|
||||
|
||||
case ENABLE_READ:
|
||||
dev->disable_access = 0;
|
||||
dev->writeonly = 0;
|
||||
return 0;
|
||||
|
||||
case DISABLE_READ:
|
||||
dev->writeonly = 1;
|
||||
return 0;
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
const struct block_device_operations rkflash_blktrans_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = rkflash_open,
|
||||
.release = rkflash_release,
|
||||
.ioctl = rkflash_ioctl,
|
||||
};
|
||||
|
||||
static struct flash_blk_ops mytr = {
|
||||
.name = "rkflash",
|
||||
.major = 31,
|
||||
.minorbits = 0,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int rkflash_add_dev(struct flash_blk_ops *blk_ops,
|
||||
struct flash_part *part)
|
||||
{
|
||||
struct flash_blk_dev *dev;
|
||||
struct gendisk *gd;
|
||||
|
||||
if (part->size == 0)
|
||||
return -1;
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return -ENOMEM;
|
||||
|
||||
gd = alloc_disk(1 << blk_ops->minorbits);
|
||||
if (!gd) {
|
||||
kfree(dev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dev->blk_ops = blk_ops;
|
||||
dev->size = part->size;
|
||||
dev->off_size = part->offset;
|
||||
dev->devnum = blk_ops->last_dev_index;
|
||||
list_add_tail(&dev->list, &blk_ops->devs);
|
||||
blk_ops->last_dev_index++;
|
||||
|
||||
gd->major = blk_ops->major;
|
||||
gd->first_minor = (dev->devnum) << blk_ops->minorbits;
|
||||
gd->fops = &rkflash_blktrans_ops;
|
||||
|
||||
if (part->name[0]) {
|
||||
snprintf(gd->disk_name,
|
||||
sizeof(gd->disk_name),
|
||||
"%s",
|
||||
part->name);
|
||||
} else {
|
||||
gd->flags = GENHD_FL_EXT_DEVT;
|
||||
gd->minors = 255;
|
||||
snprintf(gd->disk_name,
|
||||
sizeof(gd->disk_name),
|
||||
"%s%d",
|
||||
blk_ops->name,
|
||||
dev->devnum);
|
||||
}
|
||||
|
||||
set_capacity(gd, dev->size);
|
||||
|
||||
gd->private_data = dev;
|
||||
dev->blkcore_priv = gd;
|
||||
gd->queue = blk_ops->rq;
|
||||
gd->queue->bypass_depth = 1;
|
||||
|
||||
if (part->type == PART_NO_ACCESS)
|
||||
dev->disable_access = 1;
|
||||
|
||||
if (part->type == PART_READONLY)
|
||||
dev->readonly = 1;
|
||||
|
||||
if (part->type == PART_WRITEONLY)
|
||||
dev->writeonly = 1;
|
||||
|
||||
if (dev->readonly)
|
||||
set_disk_ro(gd, 1);
|
||||
|
||||
add_disk(gd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkflash_remove_dev(struct flash_blk_dev *dev)
|
||||
{
|
||||
struct gendisk *gd;
|
||||
|
||||
gd = dev->blkcore_priv;
|
||||
list_del(&dev->list);
|
||||
gd->queue = NULL;
|
||||
del_gendisk(gd);
|
||||
put_disk(gd);
|
||||
kfree(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkflash_blk_register(struct flash_blk_ops *blk_ops)
|
||||
{
|
||||
int i, ret;
|
||||
u64 offset;
|
||||
|
||||
rknand_req_do = 0;
|
||||
blk_ops->quit = 0;
|
||||
blk_ops->flash_th_quited = 0;
|
||||
|
||||
mtd_read_temp_buffer = kmalloc(MTD_RW_SECTORS * 512,
|
||||
GFP_KERNEL | GFP_DMA);
|
||||
|
||||
ret = register_blkdev(blk_ops->major, blk_ops->name);
|
||||
if (ret)
|
||||
return -1;
|
||||
|
||||
spin_lock_init(&blk_ops->queue_lock);
|
||||
init_completion(&blk_ops->thread_exit);
|
||||
init_waitqueue_head(&blk_ops->thread_wq);
|
||||
|
||||
blk_ops->rq = blk_init_queue(rkflash_blk_request, &blk_ops->queue_lock);
|
||||
if (!blk_ops->rq) {
|
||||
unregister_blkdev(blk_ops->major, blk_ops->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
blk_queue_max_hw_sectors(blk_ops->rq, MTD_RW_SECTORS);
|
||||
blk_queue_max_segments(blk_ops->rq, MTD_RW_SECTORS);
|
||||
|
||||
queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, blk_ops->rq);
|
||||
blk_queue_max_discard_sectors(blk_ops->rq, UINT_MAX >> 9);
|
||||
|
||||
blk_ops->rq->queuedata = blk_ops;
|
||||
INIT_LIST_HEAD(&blk_ops->devs);
|
||||
kthread_run(rkflash_blktrans_thread, (void *)blk_ops, "rkflash");
|
||||
g_max_part_num = rk_partition_init(disk_array);
|
||||
if (g_max_part_num) {
|
||||
/* partition 0 is save vendor data, need hidden */
|
||||
blk_ops->last_dev_index = 0;
|
||||
for (i = 1; i < g_max_part_num; i++) {
|
||||
offset = (u64)disk_array[i].offset;
|
||||
pr_info("%10s: 0x%09llx -- 0x%09llx (%llu MB)\n",
|
||||
disk_array[i].name,
|
||||
offset * 512,
|
||||
(u64)(offset + disk_array[i].size) * 512,
|
||||
(u64)disk_array[i].size / 2048);
|
||||
rkflash_add_dev(blk_ops, &disk_array[i]);
|
||||
}
|
||||
rkflash_add_dev(blk_ops, &fw_header_p);
|
||||
} else {
|
||||
struct flash_part part;
|
||||
|
||||
part.offset = 0;
|
||||
part.size = g_boot_ops[g_flash_type]->get_capacity();
|
||||
part.type = 0;
|
||||
part.name[0] = 0;
|
||||
rkflash_add_dev(&mytr, &part);
|
||||
}
|
||||
rkflash_create_procfs();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkflash_blk_unregister(struct flash_blk_ops *blk_ops)
|
||||
{
|
||||
struct list_head *this, *next;
|
||||
|
||||
blk_ops->quit = 1;
|
||||
wake_up(&blk_ops->thread_wq);
|
||||
wait_for_completion(&blk_ops->thread_exit);
|
||||
list_for_each_safe(this, next, &blk_ops->devs) {
|
||||
struct flash_blk_dev *dev =
|
||||
list_entry(this, struct flash_blk_dev, list);
|
||||
|
||||
rkflash_remove_dev(dev);
|
||||
}
|
||||
blk_cleanup_queue(blk_ops->rq);
|
||||
unregister_blkdev(blk_ops->major, blk_ops->name);
|
||||
}
|
||||
|
||||
int rkflash_dev_init(void __iomem *reg_addr, enum flash_con_type con_type)
|
||||
{
|
||||
int ret;
|
||||
int tmp_id, start_id, end_id;
|
||||
|
||||
pr_err("%s\n", __func__);
|
||||
if (rkflash_dev_initialised) {
|
||||
pr_err("rkflash has already inited as id[%d]\n", g_flash_type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (con_type == FLASH_CON_TYPE_NANDC) {
|
||||
start_id = FLASH_TYPE_NANDC_NAND;
|
||||
end_id = FLASH_TYPE_NANDC_NAND;
|
||||
} else {
|
||||
start_id = FLASH_TYPE_SFC_NOR;
|
||||
end_id = FLASH_TYPE_SFC_NAND;
|
||||
}
|
||||
for (tmp_id = start_id; tmp_id <= end_id; tmp_id++) {
|
||||
pr_info("init rkflash[%d]\n", tmp_id);
|
||||
if (g_boot_ops[tmp_id]->id == -1) {
|
||||
pr_err("rkflash[%d] is invalid\n", tmp_id);
|
||||
if (tmp_id == end_id)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
ret = g_boot_ops[tmp_id]->init(reg_addr);
|
||||
if (ret) {
|
||||
pr_err("rkflash[%d] init fail\n", tmp_id);
|
||||
if (tmp_id == end_id)
|
||||
return -1;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pr_info("rkflash[%d] init success\n", tmp_id);
|
||||
g_flash_type = tmp_id;
|
||||
mytr.quit = 1;
|
||||
flash_vendor_dev_ops_register(g_boot_ops[g_flash_type]->vendor_read,
|
||||
g_boot_ops[g_flash_type]->vendor_write);
|
||||
#ifdef CONFIG_RK_SFC_NOR_MTD
|
||||
if (g_flash_type == FLASH_TYPE_SFC_NOR) {
|
||||
pr_info("sfc_nor flash registered as a mtd device\n");
|
||||
rkflash_dev_initialised = 1;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
ret = rkflash_blk_register(&mytr);
|
||||
if (ret) {
|
||||
pr_err("rkflash_blk_register fail\n");
|
||||
g_flash_type = -1;
|
||||
return -1;
|
||||
}
|
||||
rkflash_dev_initialised = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rkflash_dev_exit(void)
|
||||
{
|
||||
if (rkflash_dev_initialised) {
|
||||
g_flash_type = -1;
|
||||
rkflash_dev_initialised = 0;
|
||||
rkflash_blk_unregister(&mytr);
|
||||
pr_info("%s:OK\n", __func__);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rkflash_dev_suspend(void)
|
||||
{
|
||||
mutex_lock(&g_flash_ops_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rkflash_dev_resume(void __iomem *reg_addr)
|
||||
{
|
||||
g_boot_ops[g_flash_type]->resume(reg_addr);
|
||||
mutex_unlock(&g_flash_ops_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rkflash_dev_shutdown(void)
|
||||
{
|
||||
pr_info("rkflash_shutdown...\n");
|
||||
if (mytr.quit == 0) {
|
||||
mytr.quit = 1;
|
||||
wake_up(&mytr.thread_wq);
|
||||
wait_for_completion(&mytr.thread_exit);
|
||||
}
|
||||
g_boot_ops[g_flash_type]->deinit();
|
||||
pr_info("rkflash_shutdown:OK\n");
|
||||
}
|
||||
126
drivers/rkflash/rkflash_blk.h
Normal file
126
drivers/rkflash/rkflash_blk.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __RKFLASH_BLK_H
|
||||
#define __RKFLASH_BLK_H
|
||||
|
||||
#include <linux/semaphore.h>
|
||||
|
||||
#define MAX_PART_COUNT 32
|
||||
#define RK_PARTITION_TAG 0x50464B52
|
||||
|
||||
enum flash_con_type {
|
||||
FLASH_CON_TYPE_NANDC = 0,
|
||||
FLASH_CON_TYPE_SFC,
|
||||
FLASH_CON_TYPE_MAX,
|
||||
};
|
||||
|
||||
enum flash_type {
|
||||
FLASH_TYPE_NANDC_NAND = 0,
|
||||
FLASH_TYPE_SFC_NOR,
|
||||
FLASH_TYPE_SFC_NAND,
|
||||
FLASH_TYPE_MAX,
|
||||
};
|
||||
|
||||
struct flash_boot_ops {
|
||||
int id;
|
||||
int (*init)(void __iomem *reg_addr);
|
||||
int (*read)(u32 sec, u32 n_sec, void *p_data);
|
||||
int (*write)(u32 sec, u32 n_sec, void *p_data);
|
||||
u32 (*get_capacity)(void);
|
||||
void (*deinit)(void);
|
||||
int (*resume)(void __iomem *reg_addr);
|
||||
int (*vendor_read)(u32 sec, u32 n_sec, void *p_data);
|
||||
int (*vendor_write)(u32 sec, u32 n_sec, void *p_data);
|
||||
};
|
||||
|
||||
struct flash_part {
|
||||
unsigned char name[32];
|
||||
unsigned int offset;
|
||||
unsigned int size;
|
||||
unsigned char type;
|
||||
};
|
||||
|
||||
struct flash_blk_ops {
|
||||
char *name;
|
||||
int major;
|
||||
int minorbits;
|
||||
int last_dev_index;
|
||||
struct completion thread_exit;
|
||||
int quit;
|
||||
int flash_th_quited;
|
||||
wait_queue_head_t thread_wq; /* thread wait queue */
|
||||
struct request_queue *rq;
|
||||
spinlock_t queue_lock; /* queue lock */
|
||||
struct list_head devs;
|
||||
struct module *owner;
|
||||
};
|
||||
|
||||
struct flash_blk_dev {
|
||||
struct flash_blk_ops *blk_ops;
|
||||
struct list_head list;
|
||||
int devnum;
|
||||
unsigned int size;
|
||||
unsigned int off_size;
|
||||
int readonly;
|
||||
int writeonly;
|
||||
int disable_access;
|
||||
void *blkcore_priv;
|
||||
};
|
||||
|
||||
enum ENUM_PARTITION_TYPE {
|
||||
PART_VENDOR = 1 << 0,
|
||||
PART_IDBLOCK = 1 << 1,
|
||||
PART_KERNEL = 1 << 2,
|
||||
PART_BOOT = 1 << 3,
|
||||
PART_USER = 1 << 31
|
||||
};
|
||||
|
||||
struct STRUCT_DATETIME {
|
||||
unsigned short year;
|
||||
unsigned char month;
|
||||
unsigned char day;
|
||||
unsigned char hour;
|
||||
unsigned char min;
|
||||
unsigned char sec;
|
||||
unsigned char reserve;
|
||||
};
|
||||
|
||||
struct STRUCT_FW_HEADER {
|
||||
unsigned int ui_fw_tag; /* "RKFP" */
|
||||
struct STRUCT_DATETIME dt_release_data_time;
|
||||
unsigned int ui_fw_ver;
|
||||
unsigned int ui_size; /* size of sturct,unit of u8 */
|
||||
unsigned int ui_part_entry_offset; /* unit of sector */
|
||||
unsigned int ui_backup_part_entry_offset;
|
||||
unsigned int ui_part_entry_size; /* unit of u8 */
|
||||
unsigned int ui_part_entry_count;
|
||||
unsigned int ui_fw_size; /* unit of u8 */
|
||||
unsigned char reserved[464];
|
||||
unsigned int ui_part_entry_crc;
|
||||
unsigned int ui_header_crc;
|
||||
};
|
||||
|
||||
struct STRUCT_PART_ENTRY {
|
||||
unsigned char sz_name[32];
|
||||
enum ENUM_PARTITION_TYPE em_part_type;
|
||||
unsigned int ui_pt_off; /* unit of sector */
|
||||
unsigned int ui_pt_sz; /* unit of sector */
|
||||
unsigned int ui_data_length; /* unit of u8 */
|
||||
unsigned int ui_part_property;
|
||||
unsigned char reserved[76];
|
||||
};
|
||||
|
||||
struct STRUCT_PART_INFO {
|
||||
struct STRUCT_FW_HEADER hdr; /* 0.5KB */
|
||||
struct STRUCT_PART_ENTRY part[12]; /* 1.5KB */
|
||||
} __packed;
|
||||
|
||||
int rkflash_dev_suspend(void);
|
||||
int rkflash_dev_resume(void __iomem *reg_addr);
|
||||
void rkflash_dev_shutdown(void);
|
||||
void rkflash_dev_flush(void);
|
||||
int rkflash_dev_init(void __iomem *reg_addr, enum flash_con_type type);
|
||||
int rkflash_dev_exit(void);
|
||||
#endif
|
||||
15
drivers/rkflash/rkflash_debug.c
Normal file
15
drivers/rkflash/rkflash_debug.c
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "rkflash_debug.h"
|
||||
#include "sfc_nor.h"
|
||||
|
||||
void rkflash_print_hex(char *s, void *buf, u32 width, u32 len)
|
||||
{
|
||||
print_hex_dump(KERN_WARNING, s, DUMP_PREFIX_OFFSET,
|
||||
16, width, buf, len * width, 0);
|
||||
}
|
||||
|
||||
44
drivers/rkflash/rkflash_debug.h
Normal file
44
drivers/rkflash/rkflash_debug.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _RKFLASH_DEBUG_H
|
||||
#define _RKFLASH_DEBUG_H
|
||||
|
||||
/*
|
||||
* Print switch, set to 1 if needed
|
||||
* I - info
|
||||
* E - error
|
||||
* HEX - multiline print
|
||||
*/
|
||||
|
||||
#define PRINT_SWI_SFC_I 0
|
||||
#define PRINT_SWI_SFC_E 1
|
||||
#define PRINT_SWI_SFC_HEX 1
|
||||
|
||||
/*
|
||||
* Test switch
|
||||
*/
|
||||
|
||||
#if (PRINT_SWI_SFC_I)
|
||||
#define PRINT_SFC_I(...) pr_info(__VA_ARGS__)
|
||||
#else
|
||||
#define PRINT_SFC_I(...)
|
||||
#endif
|
||||
|
||||
#if (PRINT_SWI_SFC_E)
|
||||
#define PRINT_SFC_E(...) pr_err(__VA_ARGS__)
|
||||
#else
|
||||
#define PRINT_SFC_E(...)
|
||||
#endif
|
||||
|
||||
#if (PRINT_SWI_SFC_HEX)
|
||||
#define PRINT_SFC_HEX(s, buf, width, len)\
|
||||
rkflash_print_hex(s, buf, width, len)
|
||||
#else
|
||||
#define PRINT_SFC_HEX(s, buf, width, len)
|
||||
#endif
|
||||
|
||||
void rkflash_print_hex(char *s, void *buf, u32 width, u32 len);
|
||||
|
||||
#endif
|
||||
193
drivers/rkflash/rknandc_base.c
Normal file
193
drivers/rkflash/rknandc_base.c
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#ifdef CONFIG_OF
|
||||
#include <linux/of.h>
|
||||
#endif
|
||||
|
||||
#include "nandc.h"
|
||||
#include "rkflash_api.h"
|
||||
#include "rkflash_blk.h"
|
||||
|
||||
#define RKNANDC_VERSION_AND_DATE "rknandc_base v1.1 2017-01-11"
|
||||
#define RKNANDC_CLK_SET_RATE (150 * 1000 * 1000)
|
||||
|
||||
struct rknandc_info {
|
||||
void __iomem *reg_base;
|
||||
int irq;
|
||||
int clk_rate;
|
||||
struct clk *clk; /* controller's clk*/
|
||||
struct clk *ahb_clk; /* ahb clk gate*/
|
||||
struct clk *g_clk; /* clk_src_en gate*/
|
||||
};
|
||||
|
||||
static struct rknandc_info g_nandc_info;
|
||||
static struct device *g_nandc_dev;
|
||||
static struct completion nandc_irq_complete;
|
||||
|
||||
unsigned long rknandc_dma_map_single(unsigned long ptr, int size, int dir)
|
||||
{
|
||||
#ifdef CONFIG_ARM64
|
||||
__dma_map_area((void *)ptr, size, dir);
|
||||
return ((unsigned long)virt_to_phys((void *)ptr));
|
||||
#else
|
||||
return dma_map_single(NULL, (void *)ptr, size
|
||||
, dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void rknandc_dma_unmap_single(unsigned long ptr, int size, int dir)
|
||||
{
|
||||
#ifdef CONFIG_ARM64
|
||||
__dma_unmap_area(phys_to_virt(ptr), size, dir);
|
||||
#else
|
||||
dma_unmap_single(NULL, (dma_addr_t)ptr, size
|
||||
, dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static irqreturn_t rknandc_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
nandc_clean_irq();
|
||||
complete(&nandc_irq_complete);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int rknandc_irq_config(int mode, void *pfun)
|
||||
{
|
||||
int ret = 0;
|
||||
int irq = g_nandc_info.irq;
|
||||
|
||||
if (mode)
|
||||
ret = request_irq(irq, pfun, 0, "rknandc",
|
||||
g_nandc_info.reg_base);
|
||||
else
|
||||
free_irq(irq, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rknandc_irq_init(void)
|
||||
{
|
||||
init_completion(&nandc_irq_complete);
|
||||
rknandc_irq_config(1, rknandc_interrupt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rknandc_irq_deinit(void)
|
||||
{
|
||||
rknandc_irq_config(0, rknandc_interrupt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rknandc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int irq;
|
||||
struct resource *mem;
|
||||
void __iomem *membase;
|
||||
int ret;
|
||||
|
||||
g_nandc_dev = &pdev->dev;
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
membase = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (!membase) {
|
||||
dev_err(&pdev->dev, "no reg resource?\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq resource?\n");
|
||||
return irq;
|
||||
}
|
||||
|
||||
g_nandc_info.irq = irq;
|
||||
g_nandc_info.reg_base = membase;
|
||||
g_nandc_info.ahb_clk = devm_clk_get(&pdev->dev, "hclk_nandc");
|
||||
g_nandc_info.clk = devm_clk_get(&pdev->dev, "clk_nandc");
|
||||
g_nandc_info.g_clk = devm_clk_get(&pdev->dev, "g_clk_nandc");
|
||||
if (unlikely(IS_ERR(g_nandc_info.clk)) ||
|
||||
unlikely(IS_ERR(g_nandc_info.ahb_clk))) {
|
||||
dev_err(&pdev->dev, "%s get clk error\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
clk_prepare_enable(g_nandc_info.g_clk);
|
||||
clk_prepare_enable(g_nandc_info.ahb_clk);
|
||||
clk_set_rate(g_nandc_info.clk, RKNANDC_CLK_SET_RATE);
|
||||
g_nandc_info.clk_rate = clk_get_rate(g_nandc_info.clk);
|
||||
clk_prepare_enable(g_nandc_info.clk);
|
||||
dev_info(&pdev->dev,
|
||||
"%s clk rate = %d\n",
|
||||
__func__,
|
||||
g_nandc_info.clk_rate);
|
||||
rknandc_irq_init();
|
||||
ret = rkflash_dev_init(g_nandc_info.reg_base, FLASH_CON_TYPE_NANDC);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rknandc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
return rkflash_dev_suspend();
|
||||
}
|
||||
|
||||
static int rknandc_resume(struct platform_device *pdev)
|
||||
{
|
||||
return rkflash_dev_resume(g_nandc_info.reg_base);
|
||||
}
|
||||
|
||||
static void rknandc_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
rkflash_dev_shutdown();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id of_rknandc_match[] = {
|
||||
{.compatible = "rockchip,nandc"},
|
||||
{}
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct platform_driver rknandc_driver = {
|
||||
.probe = rknandc_probe,
|
||||
.suspend = rknandc_suspend,
|
||||
.resume = rknandc_resume,
|
||||
.shutdown = rknandc_shutdown,
|
||||
.driver = {
|
||||
.name = "rknandc",
|
||||
#ifdef CONFIG_OF
|
||||
.of_match_table = of_rknandc_match,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static void __exit rknandc_driver_exit(void)
|
||||
{
|
||||
rkflash_dev_exit();
|
||||
rknandc_irq_deinit();
|
||||
platform_driver_unregister(&rknandc_driver);
|
||||
}
|
||||
|
||||
static int __init rknandc_driver_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
pr_err("%s\n", RKNANDC_VERSION_AND_DATE);
|
||||
ret = platform_driver_register(&rknandc_driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
module_init(rknandc_driver_init);
|
||||
module_exit(rknandc_driver_exit);
|
||||
MODULE_ALIAS(DRIVER_NAME);
|
||||
207
drivers/rkflash/rksfc_base.c
Normal file
207
drivers/rkflash/rksfc_base.c
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#ifdef CONFIG_OF
|
||||
#include <linux/of.h>
|
||||
#endif
|
||||
|
||||
#include "sfc.h"
|
||||
#include "rkflash_api.h"
|
||||
#include "rkflash_blk.h"
|
||||
|
||||
#define RKSFC_VERSION_AND_DATE "rksfc_base v1.1 2016-01-08"
|
||||
#define RKSFC_CLK_SET_RATE (100 * 1000 * 1000)
|
||||
|
||||
struct rksfc_info {
|
||||
void __iomem *reg_base;
|
||||
int irq;
|
||||
int clk_rate;
|
||||
struct clk *clk; /* sfc clk*/
|
||||
struct clk *ahb_clk; /* ahb clk gate*/
|
||||
};
|
||||
|
||||
static struct rksfc_info g_sfc_info;
|
||||
static struct device *g_sfc_dev;
|
||||
static struct completion sfc_irq_complete;
|
||||
|
||||
unsigned long rksfc_dma_map_single(unsigned long ptr, int size, int dir)
|
||||
{
|
||||
#ifdef CONFIG_ARM64
|
||||
__dma_map_area((void *)ptr, size, dir);
|
||||
return ((unsigned long)virt_to_phys((void *)ptr));
|
||||
#else
|
||||
return dma_map_single(NULL, (void *)ptr, size
|
||||
, dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void rksfc_dma_unmap_single(unsigned long ptr, int size, int dir)
|
||||
{
|
||||
#ifdef CONFIG_ARM64
|
||||
__dma_unmap_area(phys_to_virt(ptr), size, dir);
|
||||
#else
|
||||
dma_unmap_single(NULL, (dma_addr_t)ptr, size
|
||||
, dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
|
||||
#endif
|
||||
}
|
||||
|
||||
int rksfc_get_reg_addr(unsigned long *p_sfc_addr)
|
||||
{
|
||||
*p_sfc_addr = (unsigned long)g_sfc_info.reg_base;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t rksfc_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
sfc_clean_irq();
|
||||
complete(&sfc_irq_complete);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
void rksfc_irq_flag_init(void)
|
||||
{
|
||||
init_completion(&sfc_irq_complete);
|
||||
}
|
||||
|
||||
void rksfc_wait_for_irq_completed(void)
|
||||
{
|
||||
wait_for_completion_timeout(&sfc_irq_complete,
|
||||
msecs_to_jiffies(10));
|
||||
}
|
||||
|
||||
static int rksfc_irq_config(int mode, void *pfun)
|
||||
{
|
||||
int ret = 0;
|
||||
int irq = g_sfc_info.irq;
|
||||
|
||||
if (mode)
|
||||
ret = request_irq(irq, pfun, 0, "rksfc",
|
||||
g_sfc_info.reg_base);
|
||||
else
|
||||
free_irq(irq, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rksfc_irq_init(void)
|
||||
{
|
||||
init_completion(&sfc_irq_complete);
|
||||
rksfc_irq_config(1, rksfc_interrupt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rksfc_irq_deinit(void)
|
||||
{
|
||||
rksfc_irq_config(0, rksfc_interrupt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rksfc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int irq;
|
||||
struct resource *mem;
|
||||
void __iomem *membase;
|
||||
int ret;
|
||||
|
||||
g_sfc_dev = &pdev->dev;
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
membase = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (!membase) {
|
||||
dev_err(&pdev->dev, "no reg resource?\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq resource?\n");
|
||||
return irq;
|
||||
}
|
||||
|
||||
g_sfc_info.irq = irq;
|
||||
g_sfc_info.reg_base = membase;
|
||||
g_sfc_info.ahb_clk = devm_clk_get(&pdev->dev, "hclk_sfc");
|
||||
g_sfc_info.clk = devm_clk_get(&pdev->dev, "clk_sfc");
|
||||
if (unlikely(IS_ERR(g_sfc_info.clk)) ||
|
||||
unlikely(IS_ERR(g_sfc_info.ahb_clk))) {
|
||||
dev_err(&pdev->dev, "%s get clk error\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
clk_prepare_enable(g_sfc_info.ahb_clk);
|
||||
clk_set_rate(g_sfc_info.clk, RKSFC_CLK_SET_RATE);
|
||||
g_sfc_info.clk_rate = clk_get_rate(g_sfc_info.clk);
|
||||
clk_prepare_enable(g_sfc_info.clk);
|
||||
dev_info(&pdev->dev,
|
||||
"%s clk rate = %d\n",
|
||||
__func__,
|
||||
g_sfc_info.clk_rate);
|
||||
rksfc_irq_init();
|
||||
ret = rkflash_dev_init(g_sfc_info.reg_base, FLASH_CON_TYPE_SFC);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rksfc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
return rkflash_dev_suspend();
|
||||
}
|
||||
|
||||
static int rksfc_resume(struct platform_device *pdev)
|
||||
{
|
||||
return rkflash_dev_resume(g_sfc_info.reg_base);
|
||||
}
|
||||
|
||||
static void rksfc_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
rkflash_dev_shutdown();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id of_rksfc_match[] = {
|
||||
{.compatible = "rockchip,sfc"},
|
||||
{}
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct platform_driver rksfc_driver = {
|
||||
.probe = rksfc_probe,
|
||||
.suspend = rksfc_suspend,
|
||||
.resume = rksfc_resume,
|
||||
.shutdown = rksfc_shutdown,
|
||||
.driver = {
|
||||
.name = "rksfc",
|
||||
#ifdef CONFIG_OF
|
||||
.of_match_table = of_rksfc_match,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static void __exit rksfc_driver_exit(void)
|
||||
{
|
||||
rkflash_dev_exit();
|
||||
rksfc_irq_deinit();
|
||||
platform_driver_unregister(&rksfc_driver);
|
||||
}
|
||||
|
||||
static int __init rksfc_driver_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
pr_err("%s\n", RKSFC_VERSION_AND_DATE);
|
||||
ret = platform_driver_register(&rksfc_driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
module_init(rksfc_driver_init);
|
||||
module_exit(rksfc_driver_exit);
|
||||
MODULE_ALIAS(DRIVER_NAME);
|
||||
191
drivers/rkflash/sfc.c
Normal file
191
drivers/rkflash/sfc.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "sfc.h"
|
||||
|
||||
static void __iomem *g_sfc_reg;
|
||||
|
||||
static void sfc_reset(void)
|
||||
{
|
||||
int timeout = 10000;
|
||||
|
||||
writel(SFC_RESET, g_sfc_reg + SFC_RCVR);
|
||||
while ((readl(g_sfc_reg + SFC_RCVR) == SFC_RESET) && (timeout > 0)) {
|
||||
sfc_delay(1);
|
||||
timeout--;
|
||||
}
|
||||
writel(0xFFFFFFFF, g_sfc_reg + SFC_ICLR);
|
||||
}
|
||||
|
||||
u16 sfc_get_version(void)
|
||||
{
|
||||
return (u32)(readl(g_sfc_reg + SFC_VER) & 0xffff);
|
||||
}
|
||||
|
||||
int sfc_init(void __iomem *reg_addr)
|
||||
{
|
||||
g_sfc_reg = reg_addr;
|
||||
sfc_reset();
|
||||
writel(0, g_sfc_reg + SFC_CTRL);
|
||||
|
||||
return SFC_OK;
|
||||
}
|
||||
|
||||
void sfc_clean_irq(void)
|
||||
{
|
||||
writel(0xFFFFFFFF, g_sfc_reg + SFC_ICLR);
|
||||
writel(0xFFFFFFFF, g_sfc_reg + SFC_IMR);
|
||||
}
|
||||
|
||||
int sfc_request(u32 sfcmd, u32 sfctrl, u32 addr, void *data)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
union SFCCMD_DATA cmd;
|
||||
int reg;
|
||||
int timeout = 0;
|
||||
|
||||
reg = readl(g_sfc_reg + SFC_FSR);
|
||||
if (!(reg & SFC_TXEMPTY) || !(reg & SFC_RXEMPTY) ||
|
||||
(readl(g_sfc_reg + SFC_SR) & SFC_BUSY))
|
||||
sfc_reset();
|
||||
|
||||
cmd.d32 = sfcmd;
|
||||
if (cmd.b.addrbits == SFC_ADDR_XBITS) {
|
||||
union SFCCTRL_DATA ctrl;
|
||||
|
||||
ctrl.d32 = sfctrl;
|
||||
if (!ctrl.b.addrbits)
|
||||
return SFC_PARAM_ERR;
|
||||
/* Controller plus 1 automatically */
|
||||
writel(ctrl.b.addrbits - 1, g_sfc_reg + SFC_ABIT);
|
||||
}
|
||||
/* shift in the data at negedge sclk_out */
|
||||
sfctrl |= 0x2;
|
||||
|
||||
writel(sfctrl, g_sfc_reg + SFC_CTRL);
|
||||
writel(sfcmd, g_sfc_reg + SFC_CMD);
|
||||
if (cmd.b.addrbits)
|
||||
writel(addr, g_sfc_reg + SFC_ADDR);
|
||||
if (!cmd.b.datasize)
|
||||
goto exit_wait;
|
||||
if (SFC_ENABLE_DMA & sfctrl) {
|
||||
unsigned long dma_addr;
|
||||
u8 direction = (cmd.b.rw == SFC_WRITE) ? 1 : 0;
|
||||
|
||||
dma_addr = rksfc_dma_map_single((unsigned long)data,
|
||||
cmd.b.datasize,
|
||||
direction);
|
||||
rksfc_irq_flag_init();
|
||||
writel(0xFFFFFFFF, g_sfc_reg + SFC_ICLR);
|
||||
writel(~(FINISH_INT), g_sfc_reg + SFC_IMR);
|
||||
writel((u32)dma_addr, g_sfc_reg + SFC_DMA_ADDR);
|
||||
writel(SFC_DMA_START, g_sfc_reg + SFC_DMA_TRIGGER);
|
||||
|
||||
timeout = cmd.b.datasize * 10;
|
||||
rksfc_wait_for_irq_completed();
|
||||
while ((readl(g_sfc_reg + SFC_SR) & SFC_BUSY) &&
|
||||
(timeout-- > 0))
|
||||
sfc_delay(1);
|
||||
writel(0xFFFFFFFF, g_sfc_reg + SFC_ICLR);
|
||||
if (timeout <= 0)
|
||||
ret = SFC_WAIT_TIMEOUT;
|
||||
direction = (cmd.b.rw == SFC_WRITE) ? 1 : 0;
|
||||
rksfc_dma_unmap_single(dma_addr,
|
||||
cmd.b.datasize,
|
||||
direction);
|
||||
} else {
|
||||
u32 i, words, count, bytes;
|
||||
union SFCFSR_DATA fifostat;
|
||||
u32 *p_data = (u32 *)data;
|
||||
|
||||
if (cmd.b.rw == SFC_WRITE) {
|
||||
words = (cmd.b.datasize + 3) >> 2;
|
||||
while (words) {
|
||||
fifostat.d32 = readl(g_sfc_reg + SFC_FSR);
|
||||
if (fifostat.b.txlevel > 0) {
|
||||
count = words < fifostat.b.txlevel ?
|
||||
words : fifostat.b.txlevel;
|
||||
for (i = 0; i < count; i++) {
|
||||
writel(*p_data++,
|
||||
g_sfc_reg + SFC_DATA);
|
||||
words--;
|
||||
}
|
||||
if (words == 0)
|
||||
break;
|
||||
timeout = 0;
|
||||
} else {
|
||||
sfc_delay(1);
|
||||
if (timeout++ > 10000) {
|
||||
ret = SFC_TX_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* SFC_READ == cmd.b.rw */
|
||||
bytes = cmd.b.datasize & 0x3;
|
||||
words = cmd.b.datasize >> 2;
|
||||
while (words) {
|
||||
fifostat.d32 = readl(g_sfc_reg + SFC_FSR);
|
||||
if (fifostat.b.rxlevel > 0) {
|
||||
u32 count;
|
||||
|
||||
count = words < fifostat.b.rxlevel ?
|
||||
words : fifostat.b.rxlevel;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
*p_data++ = readl(g_sfc_reg +
|
||||
SFC_DATA);
|
||||
words--;
|
||||
}
|
||||
if (words == 0)
|
||||
break;
|
||||
timeout = 0;
|
||||
} else {
|
||||
sfc_delay(1);
|
||||
if (timeout++ > 10000) {
|
||||
ret = SFC_RX_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeout = 0;
|
||||
while (bytes) {
|
||||
fifostat.d32 = readl(g_sfc_reg + SFC_FSR);
|
||||
if (fifostat.b.rxlevel > 0) {
|
||||
u8 *p_data1 = (u8 *)p_data;
|
||||
|
||||
words = readl(g_sfc_reg + SFC_DATA);
|
||||
for (i = 0; i < bytes; i++)
|
||||
p_data1[i] =
|
||||
(u8)((words >> (i * 8)) & 0xFF);
|
||||
break;
|
||||
}
|
||||
|
||||
sfc_delay(1);
|
||||
if (timeout++ > 10000) {
|
||||
ret = SFC_RX_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit_wait:
|
||||
timeout = 0; /* wait cmd or data send complete */
|
||||
while (!(readl(g_sfc_reg + SFC_FSR) & SFC_TXEMPTY)) {
|
||||
sfc_delay(1);
|
||||
if (timeout++ > 100000) { /* wait 100ms */
|
||||
ret = SFC_TX_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sfc_delay(1); /* CS# High Time (read/write) >100ns */
|
||||
return ret;
|
||||
}
|
||||
179
drivers/rkflash/sfc.h
Normal file
179
drivers/rkflash/sfc.h
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _SFC_H
|
||||
#define _SFC_H
|
||||
|
||||
#define SFC_VER_3 0x3 /* ver 3, else ver 1 */
|
||||
|
||||
#define SFC_MAX_IOSIZE (1024 * 8) /* 8K byte */
|
||||
#define SFC_EN_INT (0) /* enable interrupt */
|
||||
#define SFC_EN_DMA (1) /* enable dma */
|
||||
#define SFC_FIFO_DEPTH (0x10) /* 16 words */
|
||||
|
||||
/* FIFO watermark */
|
||||
#define SFC_RX_WMARK (SFC_FIFO_DEPTH) /* RX watermark level */
|
||||
#define SFC_TX_WMARK (SFC_FIFO_DEPTH) /* TX watermark level */
|
||||
#define SFC_RX_WMARK_SHIFT (8)
|
||||
#define SFC_TX_WMARK_SHIFT (0)
|
||||
|
||||
/*return value*/
|
||||
#define SFC_OK (0)
|
||||
#define SFC_ERROR (-1)
|
||||
#define SFC_PARAM_ERR (-2)
|
||||
#define SFC_TX_TIMEOUT (-3)
|
||||
#define SFC_RX_TIMEOUT (-4)
|
||||
#define SFC_WAIT_TIMEOUT (-5)
|
||||
#define SFC_BUSY_TIMEOUT (-6)
|
||||
#define SFC_ECC_FAIL (-7)
|
||||
#define SFC_PROG_FAIL (-8)
|
||||
#define SFC_ERASE_FAIL (-9)
|
||||
|
||||
/* SFC_CMD Register */
|
||||
#define SFC_ADDR_0BITS (0)
|
||||
#define SFC_ADDR_24BITS (1)
|
||||
#define SFC_ADDR_32BITS (2)
|
||||
#define SFC_ADDR_XBITS (3)
|
||||
|
||||
#define SFC_WRITE (1)
|
||||
#define SFC_READ (0)
|
||||
|
||||
/* SFC_CTRL Register */
|
||||
#define SFC_1BITS_LINE (0)
|
||||
#define SFC_2BITS_LINE (1)
|
||||
#define SFC_4BITS_LINE (2)
|
||||
|
||||
#define SFC_ENABLE_DMA BIT(14)
|
||||
#define sfc_delay(us) udelay(us)
|
||||
|
||||
#define DMA_INT BIT(7) /* dma interrupt */
|
||||
#define NSPIERR_INT BIT(6) /* Nspi error interrupt */
|
||||
#define AHBERR_INT BIT(5) /* Ahb bus error interrupt */
|
||||
#define FINISH_INT BIT(4) /* Transfer finish interrupt */
|
||||
#define TXEMPTY_INT BIT(3) /* Tx fifo empty interrupt */
|
||||
#define TXOF_INT BIT(2) /* Tx fifo overflow interrupt */
|
||||
#define RXUF_INT BIT(1) /* Rx fifo underflow interrupt */
|
||||
#define RXFULL_INT BIT(0) /* Rx fifo full interrupt */
|
||||
|
||||
/* SFC_FSR Register*/
|
||||
#define SFC_RXFULL BIT(3) /* rx fifo full */
|
||||
#define SFC_RXEMPTY BIT(2) /* rx fifo empty */
|
||||
#define SFC_TXEMPTY BIT(1) /* tx fifo empty */
|
||||
#define SFC_TXFULL BIT(0) /* tx fifo full */
|
||||
|
||||
/* SFC_RCVR Register */
|
||||
#define SFC_RESET BIT(0) /* controller reset */
|
||||
|
||||
/* SFC_SR Register */
|
||||
/* sfc busy flag. When busy, don't try to set the control register */
|
||||
#define SFC_BUSY BIT(0)
|
||||
|
||||
/* SFC_DMA_TRIGGER Register */
|
||||
/* Dma start trigger signal. Auto cleared after write */
|
||||
#define SFC_DMA_START BIT(0)
|
||||
|
||||
#define SFC_CTRL 0x00
|
||||
#define SFC_IMR 0x04
|
||||
#define SFC_ICLR 0x08
|
||||
#define SFC_FTLR 0x0C
|
||||
#define SFC_RCVR 0x10
|
||||
#define SFC_AX 0x14
|
||||
#define SFC_ABIT 0x18
|
||||
#define SFC_MASKISR 0x1C
|
||||
#define SFC_FSR 0x20
|
||||
#define SFC_SR 0x24
|
||||
#define SFC_RAWISR 0x28
|
||||
#define SFC_VER 0x2C
|
||||
#define SFC_QOP 0x30
|
||||
#define SFC_DMA_TRIGGER 0x80
|
||||
#define SFC_DMA_ADDR 0x84
|
||||
#define SFC_CMD 0x100
|
||||
#define SFC_ADDR 0x104
|
||||
#define SFC_DATA 0x108
|
||||
|
||||
union SFCFSR_DATA {
|
||||
u32 d32;
|
||||
struct {
|
||||
unsigned txempty : 1;
|
||||
unsigned txfull : 1;
|
||||
unsigned rxempty : 1;
|
||||
unsigned rxfull : 1;
|
||||
unsigned reserved7_4 : 4;
|
||||
unsigned txlevel : 5;
|
||||
unsigned reserved15_13 : 3;
|
||||
unsigned rxlevel : 5;
|
||||
unsigned reserved31_21 : 11;
|
||||
} b;
|
||||
};
|
||||
|
||||
/*------------------------------ Global Typedefs -----------------------------*/
|
||||
enum SFC_DATA_LINES {
|
||||
DATA_LINES_X1 = 0,
|
||||
DATA_LINES_X2,
|
||||
DATA_LINES_X4
|
||||
};
|
||||
|
||||
union SFCCTRL_DATA {
|
||||
/* raw register data */
|
||||
u32 d32;
|
||||
/* register bits */
|
||||
struct {
|
||||
/* spi mode select */
|
||||
unsigned mode : 1;
|
||||
/*
|
||||
* Shift in phase selection
|
||||
* 0: shift in the flash data at posedge sclk_out
|
||||
* 1: shift in the flash data at negedge sclk_out
|
||||
*/
|
||||
unsigned sps : 1;
|
||||
unsigned reserved3_2 : 2;
|
||||
/* sclk_idle_level_cycles */
|
||||
unsigned scic : 4;
|
||||
/* Cmd bits number */
|
||||
unsigned cmdlines : 2;
|
||||
/* Address bits number */
|
||||
unsigned addrlines : 2;
|
||||
/* Data bits number */
|
||||
unsigned datalines : 2;
|
||||
/* this bit is not exit in regiseter, just use for code param */
|
||||
unsigned enbledma : 1;
|
||||
unsigned reserved15 : 1;
|
||||
unsigned addrbits : 5;
|
||||
unsigned reserved31_21 : 11;
|
||||
} b;
|
||||
};
|
||||
|
||||
union SFCCMD_DATA {
|
||||
/* raw register data */
|
||||
u32 d32;
|
||||
/* register bits */
|
||||
struct {
|
||||
/* Command that will send to Serial Flash */
|
||||
unsigned cmd : 8;
|
||||
/* Dummy bits number */
|
||||
unsigned dummybits : 4;
|
||||
/* 0: read, 1: write */
|
||||
unsigned rw : 1;
|
||||
/* Continuous read mode */
|
||||
unsigned readmode : 1;
|
||||
/* Address bits number */
|
||||
unsigned addrbits : 2;
|
||||
/* Transferred bytes number */
|
||||
unsigned datasize : 14;
|
||||
/* Chip select */
|
||||
unsigned cs : 2;
|
||||
} b;
|
||||
};
|
||||
|
||||
int sfc_init(void __iomem *reg_addr);
|
||||
int sfc_request(u32 sfcmd, u32 sfctrl, u32 addr, void *data);
|
||||
u16 sfc_get_version(void);
|
||||
void sfc_clean_irq(void);
|
||||
int rksfc_get_reg_addr(unsigned long *p_sfc_addr);
|
||||
void sfc_handle_irq(void);
|
||||
unsigned long rksfc_dma_map_single(unsigned long ptr, int size, int dir);
|
||||
void rksfc_dma_unmap_single(unsigned long ptr, int size, int dir);
|
||||
void rksfc_irq_flag_init(void);
|
||||
void rksfc_wait_for_irq_completed(void);
|
||||
#endif
|
||||
584
drivers/rkflash/sfc_nand.c
Normal file
584
drivers/rkflash/sfc_nand.c
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "flash.h"
|
||||
#include "flash_com.h"
|
||||
#include "sfc.h"
|
||||
#include "sfc_nand.h"
|
||||
|
||||
#define SFC_NAND_STRESS_TEST_EN 0
|
||||
|
||||
#define SFC_NAND_PROG_ERASE_ERROR -2
|
||||
#define SFC_NAND_HW_ERROR -1
|
||||
#define SFC_NAND_ECC_ERROR NAND_ERROR
|
||||
#define SFC_NAND_ECC_REFRESH NAND_STS_REFRESH
|
||||
#define SFC_NAND_ECC_OK NAND_STS_OK
|
||||
|
||||
#define SFC_NAND_PAGE_MAX_SIZE 2112
|
||||
|
||||
#define FEA_READ_STATUE_MASK (0x3 << 0)
|
||||
#define FEA_STATUE_MODE1 0
|
||||
#define FEA_STATUE_MODE2 1
|
||||
#define FEA_4BIT_READ BIT(2)
|
||||
#define FEA_4BIT_PROG BIT(3)
|
||||
#define FEA_4BYTE_ADDR BIT(4)
|
||||
#define FEA_4BYTE_ADDR_MODE BIT(5)
|
||||
|
||||
struct SFC_NAND_DEV_T {
|
||||
u32 capacity;
|
||||
u32 block_size;
|
||||
u16 page_size;
|
||||
u8 manufacturer;
|
||||
u8 mem_type;
|
||||
u8 read_lines;
|
||||
u8 prog_lines;
|
||||
u8 page_read_cmd;
|
||||
u8 page_prog_cmd;
|
||||
};
|
||||
|
||||
struct nand_info {
|
||||
u32 id;
|
||||
|
||||
u16 sec_per_page;
|
||||
u16 page_per_blk;
|
||||
u16 plane_per_die;
|
||||
u16 blk_per_plane;
|
||||
|
||||
u8 page_read_cmd;
|
||||
u8 page_prog_cmd;
|
||||
u8 read_cache_cmd_1;
|
||||
u8 prog_cache_cmd_1;
|
||||
|
||||
u8 read_cache_cmd_4;
|
||||
u8 prog_cache_cmd_4;
|
||||
u8 block_erase_cmd;
|
||||
u8 feature;
|
||||
|
||||
u8 density; /* (1 << density) sectors*/
|
||||
u8 max_ecc_bits;
|
||||
u8 QE_address;
|
||||
u8 QE_bits;
|
||||
|
||||
u8 spare_offs_1;
|
||||
u8 spare_offs_2;
|
||||
};
|
||||
|
||||
static struct nand_info spi_nand_tbl[] = {
|
||||
/* TC58CVG0S0HxAIx */
|
||||
{0x98C2, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x02, 0xD8, 0x00, 18, 8, 0xB0, 0XFF, 4, 8},
|
||||
/* TC58CVG1S0HxAIx */
|
||||
{0x98CB, 4, 64, 2, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x02, 0xD8, 0x00, 19, 8, 0xB0, 0XFF, 4, 8},
|
||||
/* MX35LF1GE4AB */
|
||||
{0xC212, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 18, 1, 0xB0, 0, 4, 8},
|
||||
/* MX35LF2GE4AB */
|
||||
{0xC222, 4, 64, 2, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 19, 1, 0xB0, 0, 4, 8},
|
||||
/* GD5F1GQ4UAYIG */
|
||||
{0xC8F1, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 18, 1, 0xB0, 0, 4, 8},
|
||||
/* GD5F2GQ40BY2GR */
|
||||
{0xC8D2, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 18, 1, 0xB0, 0, 4, 8},
|
||||
/* MT29F1G01ZAC */
|
||||
{0x2C12, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x00, 18, 1, 0xB0, 0, 4, 8},
|
||||
/* GD5F1GQ4U */
|
||||
{0xC8B1, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 18, 1, 0xB0, 0, 4, 8},
|
||||
/* GD5F2GQ4U */
|
||||
{0xC8B2, 4, 64, 2, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 19, 1, 0xB0, 0, 4, 8},
|
||||
/* GD5F1GQ4U */
|
||||
{0xC8D1, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 18, 1, 0xB0, 0, 4, 8},
|
||||
/* IS37SML01G1 */
|
||||
{0xC821, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x00, 18, 1, 0xB0, 0XFF, 8, 12},
|
||||
/* W25N01GV */
|
||||
{0xEFAA, 4, 64, 1, 1024, 0x13, 0x10, 0x03, 0x02, 0x6B, 0x32, 0xD8, 0x0C, 18, 1, 0xFF, 0XFF, 4, 20},
|
||||
};
|
||||
|
||||
static u8 id_byte[8];
|
||||
static struct nand_info *p_nand_info;
|
||||
static u32 gp_page_buf[SFC_NAND_PAGE_MAX_SIZE / 4];
|
||||
static struct SFC_NAND_DEV_T sfc_nand_dev;
|
||||
|
||||
static struct nand_info *spi_nand_get_info(u8 *nand_id)
|
||||
{
|
||||
u32 i;
|
||||
u32 id = (nand_id[0] << 8) | (nand_id[1] << 0);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(spi_nand_tbl); i++) {
|
||||
if (spi_nand_tbl[i].id == id)
|
||||
return &spi_nand_tbl[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int sfc_nand_write_en(void)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_WRITE_EN;
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sfc_nand_rw_preset(void)
|
||||
{
|
||||
int ret;
|
||||
union SFCCTRL_DATA sfctrl;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
u8 status = 0xFF;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = 0;
|
||||
sfcmd.b.datasize = 1;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
|
||||
sfctrl.b.datalines = 2;
|
||||
ret = sfc_request(sfcmd.d32, sfctrl.d32, 0, &status);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sfc_nand_read_feature(u8 addr, u8 *data)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = 0x0F;
|
||||
sfcmd.b.datasize = 1;
|
||||
sfcmd.b.addrbits = SFC_ADDR_XBITS;
|
||||
*data = 0;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0x8 << 16, addr, data);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
return SFC_OK;
|
||||
}
|
||||
|
||||
static int sfc_nand_write_feature(u32 addr, u8 status)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfc_nand_write_en();
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = 0x1F;
|
||||
sfcmd.b.datasize = 1;
|
||||
sfcmd.b.addrbits = SFC_ADDR_XBITS;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0x8 << 16, addr, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sfc_nand_wait_busy(u8 *data, int timeout)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
u8 status;
|
||||
|
||||
*data = 0;
|
||||
for (i = 0; i < timeout; i++) {
|
||||
ret = sfc_nand_read_feature(0xC0, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
*data = status;
|
||||
if (!(status & (1 << 0)))
|
||||
return SFC_OK;
|
||||
sfc_delay(1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static u32 sfc_nand_erase_block(u8 cs, u32 addr)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
u8 status;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = p_nand_info->block_erase_cmd;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
sfc_nand_write_en();
|
||||
ret = sfc_request(sfcmd.d32, 0, addr, NULL);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
ret = sfc_nand_wait_busy(&status, 1000 * 1000);
|
||||
if (status & (1 << 2))
|
||||
return SFC_NAND_PROG_ERASE_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u32 sfc_nand_prog_page(u8 cs, u32 addr, u32 *p_data, u32 *p_spare)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
union SFCCTRL_DATA sfctrl;
|
||||
u8 status;
|
||||
u32 data_sz = 2048;
|
||||
u32 spare_offs_1 = p_nand_info->spare_offs_1;
|
||||
u32 spare_offs_2 = p_nand_info->spare_offs_2;
|
||||
|
||||
memcpy(gp_page_buf, p_data, data_sz);
|
||||
gp_page_buf[(data_sz + spare_offs_1) / 4] = p_spare[0];
|
||||
gp_page_buf[(data_sz + spare_offs_2) / 4] = p_spare[1];
|
||||
|
||||
sfc_nand_write_en();
|
||||
if (sfc_nand_dev.prog_lines == DATA_LINES_X4 &&
|
||||
p_nand_info->QE_address == 0xFF &&
|
||||
sfc_get_version() != SFC_VER_3)
|
||||
sfc_nand_rw_preset();
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = sfc_nand_dev.page_prog_cmd;
|
||||
sfcmd.b.addrbits = SFC_ADDR_XBITS;
|
||||
sfcmd.b.datasize = SFC_NAND_PAGE_MAX_SIZE;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
|
||||
sfctrl.d32 = 0;
|
||||
sfctrl.b.datalines = sfc_nand_dev.prog_lines;
|
||||
sfctrl.b.addrbits = 16;
|
||||
ret = sfc_request(sfcmd.d32, sfctrl.d32, 0, gp_page_buf);
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = p_nand_info->page_prog_cmd;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
sfcmd.b.datasize = 0;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
ret = sfc_request(sfcmd.d32, 0, addr, p_data);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
ret = sfc_nand_wait_busy(&status, 1000 * 1000);
|
||||
if (status & (1 << 3))
|
||||
return SFC_NAND_PROG_ERASE_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u32 sfc_nand_read_page(u8 cs, u32 addr, u32 *p_data, u32 *p_spare)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
union SFCCTRL_DATA sfctrl;
|
||||
u8 status;
|
||||
u8 ecc;
|
||||
u32 data_sz = 2048;
|
||||
u32 spare_offs_1 = p_nand_info->spare_offs_1;
|
||||
u32 spare_offs_2 = p_nand_info->spare_offs_2;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = p_nand_info->page_read_cmd;
|
||||
sfcmd.b.datasize = 0;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
ret = sfc_request(sfcmd.d32, 0, addr, p_data);
|
||||
|
||||
ret = sfc_nand_wait_busy(&status, 1000 * 1000);
|
||||
ecc = (status >> 4) & 0x03;
|
||||
if (sfc_nand_dev.read_lines == DATA_LINES_X4 &&
|
||||
p_nand_info->QE_address == 0xFF &&
|
||||
sfc_get_version() != SFC_VER_3)
|
||||
sfc_nand_rw_preset();
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = sfc_nand_dev.page_read_cmd;
|
||||
sfcmd.b.datasize = SFC_NAND_PAGE_MAX_SIZE;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
sfctrl.d32 = 0;
|
||||
sfctrl.b.datalines = sfc_nand_dev.read_lines;
|
||||
|
||||
memset(gp_page_buf, 0, SFC_NAND_PAGE_MAX_SIZE);
|
||||
ret = sfc_request(sfcmd.d32, sfctrl.d32, 0, gp_page_buf);
|
||||
|
||||
memcpy(p_data, gp_page_buf, data_sz);
|
||||
p_spare[0] = gp_page_buf[(data_sz + spare_offs_1) / 4];
|
||||
p_spare[1] = gp_page_buf[(data_sz + spare_offs_2) / 4];
|
||||
if (ret != SFC_OK)
|
||||
return SFC_NAND_ECC_ERROR;
|
||||
|
||||
/*
|
||||
* ecc status:
|
||||
* 0, No bit errors were detected
|
||||
* 1, Bit errors were detected and corrected. If max_ecc_bits equals 1,
|
||||
* Bit error count exceed the bit flip detection threshold.
|
||||
* 2, Multiple bit errors were detected and not corrected.
|
||||
* 3, If max_ecc_bits equals 1, reserved, else bit errors were detected
|
||||
* and corrected, bit error count exceed the bit flip detection
|
||||
* threshold
|
||||
*/
|
||||
|
||||
if (ecc == 0) {
|
||||
ret = SFC_NAND_ECC_OK;
|
||||
} else if (ecc == 1) {
|
||||
if (p_nand_info->max_ecc_bits == 1)
|
||||
ret = SFC_NAND_ECC_REFRESH;
|
||||
else
|
||||
ret = SFC_NAND_ECC_OK;
|
||||
} else if (ecc == 2) {
|
||||
ret = SFC_NAND_ECC_ERROR;
|
||||
} else {
|
||||
if (p_nand_info->max_ecc_bits == 1)
|
||||
ret = SFC_NAND_ECC_ERROR;
|
||||
else
|
||||
ret = SFC_NAND_ECC_REFRESH;
|
||||
}
|
||||
|
||||
if (ret != SFC_NAND_ECC_OK) {
|
||||
PRINT_E("%s[0x%x], ret=0x%x\n", __func__, addr, ret);
|
||||
if (p_data)
|
||||
rknand_print_hex("data:", p_data, 4, 8);
|
||||
if (p_spare)
|
||||
rknand_print_hex("spare:", p_spare, 4, 2);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sfc_nand_read_id_raw(u8 *data)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_READ_JEDECID;
|
||||
sfcmd.b.datasize = 3;
|
||||
sfcmd.b.addrbits = SFC_ADDR_XBITS;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0x8 << 16, 0, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the 1st page's 1st byte of a phy_blk
|
||||
* If not FF, it's bad blk
|
||||
*/
|
||||
static int sfc_nand_get_bad_block_list(u16 *table, u32 die)
|
||||
{
|
||||
u16 blk;
|
||||
u32 bad_cnt, page;
|
||||
u32 blk_per_die;
|
||||
u32 *pread;
|
||||
u32 *pspare_read;
|
||||
|
||||
PRINT_E("%s\n", __func__);
|
||||
pread = ftl_malloc(2048);
|
||||
pspare_read = ftl_malloc(8);
|
||||
bad_cnt = 0;
|
||||
blk_per_die = p_nand_info->plane_per_die *
|
||||
p_nand_info->blk_per_plane;
|
||||
for (blk = 0; blk < blk_per_die; blk++) {
|
||||
page = (blk + blk_per_die * die) *
|
||||
p_nand_info->page_per_blk;
|
||||
sfc_nand_read_page(0, page, pread, pspare_read);
|
||||
|
||||
if (pread[0] != 0xFFFFFFFF ||
|
||||
pspare_read[0] != 0xFFFFFFFF) {
|
||||
table[bad_cnt++] = blk;
|
||||
PRINT_E("die[%d], bad_blk[%d]\n", die, blk);
|
||||
}
|
||||
}
|
||||
ftl_free(pread);
|
||||
ftl_free(pspare_read);
|
||||
return (int)bad_cnt;
|
||||
}
|
||||
|
||||
#if SFC_NAND_STRESS_TEST_EN
|
||||
|
||||
#define SFC_NAND_PAGE_SIZE 2048
|
||||
#define SFC_NAND_SPARE_SIZE 8
|
||||
|
||||
static u16 bad_blk_list[1024];
|
||||
static u32 pwrite[SFC_NAND_PAGE_SIZE / 4];
|
||||
static u32 pread[SFC_NAND_PAGE_SIZE / 4];
|
||||
static u32 pspare_write[SFC_NAND_SPARE_SIZE / 4];
|
||||
static u32 pspare_read[SFC_NAND_SPARE_SIZE / 4];
|
||||
static u32 bad_blk_num;
|
||||
static u32 bad_page_num;
|
||||
|
||||
static void sfc_nand_test(void)
|
||||
{
|
||||
u32 i, blk, page, bad_cnt, page_addr;
|
||||
int ret;
|
||||
u32 pages_num = 64;
|
||||
u32 blk_addr = 64;
|
||||
u32 is_bad_blk = 0;
|
||||
|
||||
PRINT_E("%s\n", __func__);
|
||||
|
||||
bad_blk_num = 0;
|
||||
bad_page_num = 0;
|
||||
bad_cnt = sfc_nand_get_bad_block_list(bad_blk_list, 0);
|
||||
|
||||
for (blk = 0; blk < 1024; blk++) {
|
||||
for (i = 0; i < bad_cnt; i++) {
|
||||
if (bad_blk_list[i] == blk)
|
||||
break;
|
||||
}
|
||||
if (i < bad_cnt)
|
||||
continue;
|
||||
is_bad_blk = 0;
|
||||
PRINT_E("Flash prog block: %x\n", blk);
|
||||
sfc_nand_erase_block(0, blk * blk_addr);
|
||||
for (page = 0; page < pages_num; page++) {
|
||||
page_addr = blk * blk_addr + page;
|
||||
for (i = 0; i < 512; i++)
|
||||
pwrite[i] = (page_addr << 16) + i;
|
||||
pspare_write[0] = pwrite[0] + 0x5AF0;
|
||||
pspare_write[1] = pspare_write[0] + 1;
|
||||
sfc_nand_prog_page(0, page_addr, pwrite, pspare_write);
|
||||
memset(pread, 0, 2048);
|
||||
memset(pspare_read, 0, 8);
|
||||
ret = sfc_nand_read_page(0, page_addr, pread,
|
||||
pspare_read);
|
||||
if (ret != SFC_NAND_ECC_OK)
|
||||
is_bad_blk = 1;
|
||||
for (i = 0; i < 512; i++) {
|
||||
if (pwrite[i] != pread[i]) {
|
||||
is_bad_blk = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (pspare_write[i] != pspare_read[i]) {
|
||||
is_bad_blk = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_bad_blk) {
|
||||
bad_page_num++;
|
||||
PRINT_E("ERR:page%x, ret=%x\n", page_addr, ret);
|
||||
rknand_print_hex("data:", pread, 4, 8);
|
||||
rknand_print_hex("spare:", pspare_read, 4, 2);
|
||||
}
|
||||
}
|
||||
sfc_nand_erase_block(0, blk * blk_addr);
|
||||
if (is_bad_blk)
|
||||
bad_blk_num++;
|
||||
}
|
||||
PRINT_E("bad_blk_num = %d, bad_page_num = %d\n",
|
||||
bad_blk_num, bad_page_num);
|
||||
|
||||
PRINT_E("Flash Test Finish!!!\n");
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ftl_flash_init(void)
|
||||
{
|
||||
/* para init */
|
||||
g_nand_phy_info.nand_type = 1;
|
||||
g_nand_phy_info.die_num = 1;
|
||||
g_nand_phy_info.plane_per_die = p_nand_info->plane_per_die;
|
||||
g_nand_phy_info.blk_per_plane = p_nand_info->blk_per_plane;
|
||||
g_nand_phy_info.page_per_blk = p_nand_info->page_per_blk;
|
||||
g_nand_phy_info.page_per_slc_blk = p_nand_info->page_per_blk;
|
||||
g_nand_phy_info.byte_per_sec = 512;
|
||||
g_nand_phy_info.sec_per_page = p_nand_info->sec_per_page;
|
||||
g_nand_phy_info.sec_per_blk = p_nand_info->sec_per_page *
|
||||
p_nand_info->page_per_blk;
|
||||
g_nand_phy_info.reserved_blk = 8;
|
||||
g_nand_phy_info.blk_per_die = p_nand_info->plane_per_die *
|
||||
p_nand_info->blk_per_plane;
|
||||
g_nand_phy_info.ecc_bits = p_nand_info->max_ecc_bits;
|
||||
|
||||
/* driver register */
|
||||
g_nand_ops.get_bad_blk_list = sfc_nand_get_bad_block_list;
|
||||
g_nand_ops.erase_blk = sfc_nand_erase_block;
|
||||
g_nand_ops.prog_page = sfc_nand_prog_page;
|
||||
g_nand_ops.read_page = sfc_nand_read_page;
|
||||
}
|
||||
|
||||
static int spi_nand_enable_QE(void)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
u8 status;
|
||||
int bit_offset = p_nand_info->QE_bits;
|
||||
|
||||
if (bit_offset == 0xFF)
|
||||
return SFC_OK;
|
||||
|
||||
ret = sfc_nand_read_feature(p_nand_info->QE_address, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
if (status & (1 << bit_offset)) /* is QE bit set */
|
||||
return SFC_OK;
|
||||
|
||||
status |= (1 << bit_offset);
|
||||
return sfc_nand_write_feature(p_nand_info->QE_address, status);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 sfc_nand_init(void __iomem *sfc_addr)
|
||||
{
|
||||
PRINT_E("%s\n", __func__);
|
||||
sfc_init(sfc_addr);
|
||||
sfc_nand_read_id_raw(id_byte);
|
||||
PRINT_E("sfc_nand id: %x %x %x\n", id_byte[0], id_byte[1], id_byte[2]);
|
||||
if (id_byte[0] == 0xFF || id_byte[0] == 0x00)
|
||||
return FTL_NO_FLASH;
|
||||
|
||||
p_nand_info = spi_nand_get_info(id_byte);
|
||||
if (!p_nand_info)
|
||||
return FTL_UNSUPPORTED_FLASH;
|
||||
|
||||
sfc_nand_dev.manufacturer = id_byte[0];
|
||||
sfc_nand_dev.mem_type = id_byte[1];
|
||||
|
||||
/* disable block lock */
|
||||
sfc_nand_write_feature(0xA0, 0);
|
||||
sfc_nand_dev.read_lines = DATA_LINES_X1;
|
||||
sfc_nand_dev.prog_lines = DATA_LINES_X1;
|
||||
sfc_nand_dev.page_read_cmd = p_nand_info->read_cache_cmd_1;
|
||||
sfc_nand_dev.page_prog_cmd = p_nand_info->prog_cache_cmd_1;
|
||||
if (p_nand_info->feature & FEA_4BIT_READ) {
|
||||
if (spi_nand_enable_QE() == SFC_OK) {
|
||||
sfc_nand_dev.read_lines = DATA_LINES_X4;
|
||||
sfc_nand_dev.page_read_cmd =
|
||||
p_nand_info->read_cache_cmd_4;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_nand_info->feature & FEA_4BIT_PROG &&
|
||||
sfc_nand_dev.read_lines == DATA_LINES_X4) {
|
||||
sfc_nand_dev.prog_lines = DATA_LINES_X4;
|
||||
sfc_nand_dev.page_prog_cmd = p_nand_info->prog_cache_cmd_4;
|
||||
}
|
||||
|
||||
if (1) {
|
||||
u8 status;
|
||||
|
||||
sfc_nand_read_feature(0xA0, &status);
|
||||
PRINT_E("sfc_nand A0 = 0x%x\n", status);
|
||||
sfc_nand_read_feature(0xB0, &status);
|
||||
PRINT_E("sfc_nand B0 = 0x%x\n", status);
|
||||
sfc_nand_read_feature(0xC0, &status);
|
||||
PRINT_E("sfc_nand C0 = 0x%x\n", status);
|
||||
PRINT_E("read_lines = %x\n", sfc_nand_dev.read_lines);
|
||||
PRINT_E("prog_lines = %x\n", sfc_nand_dev.prog_lines);
|
||||
PRINT_E("page_read_cmd = %x\n", sfc_nand_dev.page_read_cmd);
|
||||
PRINT_E("page_prog_cmd = %x\n", sfc_nand_dev.page_prog_cmd);
|
||||
}
|
||||
ftl_flash_init();
|
||||
|
||||
#if SFC_NAND_STRESS_TEST_EN
|
||||
sfc_nand_test();
|
||||
#endif
|
||||
|
||||
return SFC_OK;
|
||||
}
|
||||
|
||||
void sfc_nand_deinit(void)
|
||||
{
|
||||
}
|
||||
|
||||
int sfc_nand_read_id(u8 *data)
|
||||
{
|
||||
memcpy(data, id_byte, 3);
|
||||
return 0;
|
||||
}
|
||||
65
drivers/rkflash/sfc_nand.h
Normal file
65
drivers/rkflash/sfc_nand.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __SFC_NAND_H
|
||||
#define __SFC_NAND_H
|
||||
|
||||
/* Manufactory ID */
|
||||
#define MID_WINBOND 0xEF
|
||||
#define MID_GIGADEV 0xC8
|
||||
#define MID_MICRON 0x2C
|
||||
#define MID_MACRONIX 0xC2
|
||||
#define MID_SPANSION 0x01
|
||||
#define MID_EON 0x1C
|
||||
#define MID_ST 0x20
|
||||
|
||||
/* Command Set */
|
||||
#define CMD_READ_JEDECID (0x9F)
|
||||
#define CMD_READ_DATA (0x03)
|
||||
#define CMD_READ_STATUS (0x05)
|
||||
#define CMD_WRITE_STATUS (0x01)
|
||||
#define CMD_PAGE_PROG (0x02)
|
||||
#define CMD_SECTOR_ERASE (0x20)
|
||||
#define CMD_BLK64K_ERASE (0xD8)
|
||||
#define CMD_BLK32K_ERASE (0x52)
|
||||
#define CMD_CHIP_ERASE (0xC7)
|
||||
#define CMD_WRITE_EN (0x06)
|
||||
#define CMD_WRITE_DIS (0x04)
|
||||
#define CMD_PAGE_READ (0x13)
|
||||
#define CMD_GET_FEATURE (0x0F)
|
||||
#define CMD_SET_FEATURE (0x1F)
|
||||
#define CMD_PROG_LOAD (0x02)
|
||||
#define CMD_PROG_EXEC (0x10)
|
||||
#define CMD_BLOCK_ERASE (0xD8)
|
||||
#define CMD_READ_DATA_X2 (0x3B)
|
||||
#define CMD_READ_DATA_X4 (0x6B)
|
||||
#define CMD_PROG_LOAD_X4 (0x32)
|
||||
#define CMD_READ_STATUS2 (0x35)
|
||||
#define CMD_READ_STATUS3 (0x15)
|
||||
#define CMD_WRITE_STATUS2 (0x31)
|
||||
#define CMD_WRITE_STATUS3 (0x11)
|
||||
#define CMD_FAST_READ_X1 (0x0B) /* X1 cmd, X1 addr, X1 data */
|
||||
#define CMD_FAST_READ_X2 (0x3B) /* X1 cmd, X1 addr, X2 data */
|
||||
/* X1 cmd, X1 addr, X4 data SUPPORT GD MARCONIX WINBOND */
|
||||
#define CMD_FAST_READ_X4 (0x6B)
|
||||
/* X1 cmd, X1 addr, X4 data SUPPORT GD MARCONIX WINBOND */
|
||||
#define CMD_FAST_4READ_X4 (0x6C)
|
||||
/* X1 cmd, X4 addr, X4 data SUPPORT EON GD MARCONIX WINBOND */
|
||||
#define CMD_FAST_READ_A4 (0xEB)
|
||||
/* X1 cmd, X1 addr, X4 data, SUPPORT GD WINBOND */
|
||||
#define CMD_PAGE_PROG_X4 (0x32)
|
||||
/* X1 cmd, X4 addr, X4 data, SUPPORT MARCONIX */
|
||||
#define CMD_PAGE_PROG_A4 (0x38)
|
||||
#define CMD_RESET_NAND (0xFF)
|
||||
|
||||
#define CMD_ENTER_4BYTE_MODE (0xB7)
|
||||
#define CMD_EXIT_4BYTE_MODE (0xE9)
|
||||
#define CMD_ENABLE_RESER (0x66)
|
||||
#define CMD_RESET_DEVICE (0x99)
|
||||
|
||||
u32 sfc_nand_init(void __iomem *sfc_addr);
|
||||
void sfc_nand_deinit(void);
|
||||
int sfc_nand_read_id(u8 *buf);
|
||||
|
||||
#endif
|
||||
49
drivers/rkflash/sfc_nand_boot.c
Normal file
49
drivers/rkflash/sfc_nand_boot.c
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include "rk_sftl.h"
|
||||
#include "sfc_nand.h"
|
||||
#include "sfc_nand_boot.h"
|
||||
#include "typedef.h"
|
||||
|
||||
int snand_init(void __iomem *reg_addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sfc_nand_init(reg_addr);
|
||||
if (ret == 0)
|
||||
ret = sftl_init();
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snand_init);
|
||||
|
||||
unsigned int snand_get_capacity(void)
|
||||
{
|
||||
return sftl_get_density();
|
||||
}
|
||||
|
||||
int snand_write(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
return sftl_write(sec, n_sec, p_data);
|
||||
}
|
||||
|
||||
int snand_read(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
return sftl_read(sec, n_sec, p_data);
|
||||
}
|
||||
|
||||
void snand_deinit(void)
|
||||
{
|
||||
sftl_deinit();
|
||||
sfc_nand_deinit();
|
||||
}
|
||||
|
||||
int snand_resume(void __iomem *reg_addr)
|
||||
{
|
||||
return sfc_nand_init(reg_addr);
|
||||
}
|
||||
16
drivers/rkflash/sfc_nand_boot.h
Normal file
16
drivers/rkflash/sfc_nand_boot.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _SFC_NOR_BOOT_H
|
||||
#define _SFC_NOR_BOOT_H
|
||||
|
||||
int snand_init(void __iomem *reg_addr);
|
||||
int snand_read(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
int snand_write(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
unsigned int snand_get_capacity(void);
|
||||
void snand_deinit(void);
|
||||
int snand_resume(void __iomem *reg_addr);
|
||||
void sfc_clean_irq(void);
|
||||
|
||||
#endif
|
||||
656
drivers/rkflash/sfc_nor.c
Normal file
656
drivers/rkflash/sfc_nor.c
Normal file
|
|
@ -0,0 +1,656 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include "sfc_nor.h"
|
||||
#include "rkflash_debug.h"
|
||||
|
||||
static struct flash_info spi_flash_tbl[] = {
|
||||
/* GD25Q32B */
|
||||
{0xc84016, 128, 8, 0x03, 0x02, 0x6B, 0x32, 0x20, 0xD8, 0x0D, 13, 9, 0},
|
||||
/* GD25Q64B */
|
||||
{0xc84017, 128, 8, 0x03, 0x02, 0x6B, 0x32, 0x20, 0xD8, 0x0D, 14, 9, 0},
|
||||
/* GD25Q127C and GD25Q128C*/
|
||||
{0xc84018, 128, 8, 0x03, 0x02, 0x6B, 0x32, 0x20, 0xD8, 0x0C, 15, 9, 0},
|
||||
/* GD25Q256B */
|
||||
{0xc84019, 128, 8, 0x13, 0x12, 0x6C, 0x3E, 0x21, 0xDC, 0x1C, 16, 6, 0},
|
||||
/* GD25Q512MC */
|
||||
{0xc84020, 128, 8, 0x13, 0x12, 0x6C, 0x3E, 0x21, 0xDC, 0x1C, 17, 6, 0},
|
||||
/* 25Q128FV */
|
||||
{0xef4018, 128, 8, 0x03, 0x02, 0x6B, 0x32, 0x20, 0xD8, 0x0C, 15, 9, 0},
|
||||
/* 25Q256FV */
|
||||
{0xef4019, 128, 8, 0x13, 0x02, 0x6C, 0x32, 0x20, 0xD8, 0x3C, 16, 9, 0},
|
||||
/* XT25F128A */
|
||||
{0x207018, 128, 8, 0x03, 0x02, 0x6B, 0x32, 0x20, 0xD8, 0x00, 15, 0, 0},
|
||||
/* MX25L25635E/F */
|
||||
{0xc22019, 128, 8, 0x03, 0x02, 0x6B, 0x38, 0x20, 0xD8, 0x30, 16, 6, 0},
|
||||
};
|
||||
|
||||
static const u8 sfnor_dev_code[] = {
|
||||
0x11,
|
||||
0x12,
|
||||
0x13,
|
||||
0x14,
|
||||
0x15,
|
||||
0x16,
|
||||
0x17,
|
||||
0x18,
|
||||
0x19
|
||||
};
|
||||
|
||||
static const u32 sfnor_capacity[] = {
|
||||
0x20000, /* 128k-byte */
|
||||
0x40000, /* 256k-byte */
|
||||
0x80000, /* 512k-byte */
|
||||
0x100000, /* 1M-byte */
|
||||
0x200000, /* 2M-byte */
|
||||
0x400000, /* 4M-byte */
|
||||
0x800000, /* 8M-byte */
|
||||
0x1000000, /* 16M-byte */
|
||||
0x2000000 /* 32M-byte */
|
||||
};
|
||||
|
||||
struct SFNOR_DEV sfnor_dev;
|
||||
struct flash_info *g_spi_flash_info;
|
||||
|
||||
static int snor_write_en(void)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_WRITE_EN;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_reset_device(void)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_ENABLE_RESER;
|
||||
sfc_request(sfcmd.d32, 0, 0, NULL);
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_RESET_DEVICE;
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, NULL);
|
||||
/* tRST=30us , delay 1ms here */
|
||||
mdelay(1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_enter_4byte_mode(void)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_ENTER_4BYTE_MODE;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_read_status(u32 reg_index, u8 *status)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
u8 read_stat_cmd[] = {CMD_READ_STATUS,
|
||||
CMD_READ_STATUS2, CMD_READ_STATUS3};
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = read_stat_cmd[reg_index];
|
||||
sfcmd.b.datasize = 1;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, status);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_wait_busy(int timeout)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
int i;
|
||||
u32 status;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_READ_STATUS;
|
||||
sfcmd.b.datasize = 1;
|
||||
|
||||
for (i = 0; i < timeout; i++) {
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
if ((status & 0x01) == 0)
|
||||
return SFC_OK;
|
||||
|
||||
sfc_delay(1);
|
||||
}
|
||||
PRINT_SFC_E("%s error %x\n", __func__, timeout);
|
||||
|
||||
return SFC_BUSY_TIMEOUT;
|
||||
}
|
||||
|
||||
static int snor_write_status2(u32 reg_index, u8 status)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
u8 status2[2];
|
||||
u8 read_index;
|
||||
|
||||
status2[reg_index] = status;
|
||||
read_index = (reg_index == 0) ? 1 : 0;
|
||||
ret = snor_read_status(read_index, &status2[read_index]);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
snor_write_en();
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_WRITE_STATUS;
|
||||
sfcmd.b.datasize = 2;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, &status2[0]);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
ret = snor_wait_busy(10000); /* 10ms */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_write_status(u32 reg_index, u8 status)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
u8 write_stat_cmd[] = {CMD_WRITE_STATUS,
|
||||
CMD_WRITE_STATUS2, CMD_WRITE_STATUS3};
|
||||
snor_write_en();
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = write_stat_cmd[reg_index];
|
||||
sfcmd.b.datasize = 1;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
ret = snor_wait_busy(10000); /* 10ms */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_erase(struct SFNOR_DEV *p_dev,
|
||||
u32 addr,
|
||||
enum NOR_ERASE_TYPE erase_type)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
int timeout[] = {400, 2000, 40000}; /* ms */
|
||||
|
||||
if (erase_type > ERASE_CHIP)
|
||||
return SFC_PARAM_ERR;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
if (erase_type == ERASE_BLOCK64K)
|
||||
sfcmd.b.cmd = p_dev->blk_erase_cmd;
|
||||
else if (erase_type == ERASE_SECTOR)
|
||||
sfcmd.b.cmd = p_dev->sec_erase_cmd;
|
||||
else
|
||||
sfcmd.b.cmd = CMD_CHIP_ERASE;
|
||||
|
||||
sfcmd.b.addrbits = (erase_type != ERASE_CHIP) ?
|
||||
SFC_ADDR_24BITS : SFC_ADDR_0BITS;
|
||||
if (p_dev->addr_mode == ADDR_MODE_4BYTE && erase_type != ERASE_CHIP)
|
||||
sfcmd.b.addrbits = SFC_ADDR_32BITS;
|
||||
|
||||
snor_write_en();
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, addr, NULL);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
ret = snor_wait_busy(timeout[erase_type] * 1000);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_prog_page(struct SFNOR_DEV *p_dev,
|
||||
u32 addr,
|
||||
void *p_data,
|
||||
u32 size)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
union SFCCTRL_DATA sfctrl;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = p_dev->prog_cmd;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
sfcmd.b.datasize = size;
|
||||
sfcmd.b.rw = SFC_WRITE;
|
||||
|
||||
sfctrl.d32 = 0;
|
||||
sfctrl.b.datalines = p_dev->prog_lines;
|
||||
sfctrl.b.enbledma = 0;
|
||||
if (p_dev->prog_cmd == CMD_PAGE_PROG_A4)
|
||||
sfctrl.b.addrlines = SFC_4BITS_LINE;
|
||||
|
||||
if (p_dev->addr_mode == ADDR_MODE_4BYTE)
|
||||
sfcmd.b.addrbits = SFC_ADDR_32BITS;
|
||||
|
||||
snor_write_en();
|
||||
|
||||
ret = sfc_request(sfcmd.d32, sfctrl.d32, addr, p_data);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
ret = snor_wait_busy(10000);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_prog(struct SFNOR_DEV *p_dev, u32 addr, void *p_data, u32 size)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
u32 page_size, len;
|
||||
u8 *p_buf = (u8 *)p_data;
|
||||
|
||||
page_size = NOR_PAGE_SIZE;
|
||||
while (size) {
|
||||
len = page_size < size ? page_size : size;
|
||||
ret = snor_prog_page(p_dev, addr, p_buf, len);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
size -= len;
|
||||
addr += len;
|
||||
p_buf += len;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_enable_QE(struct SFNOR_DEV *p_dev)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
int reg_index;
|
||||
int bit_offset;
|
||||
u8 status;
|
||||
|
||||
if (p_dev->manufacturer == MID_GIGADEV ||
|
||||
p_dev->manufacturer == MID_WINBOND) {
|
||||
reg_index = p_dev->QE_bits >> 3;
|
||||
bit_offset = p_dev->QE_bits & 0x7;
|
||||
ret = snor_read_status(reg_index, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
if (status & (1 << bit_offset)) /* is QE bit set */
|
||||
return SFC_OK;
|
||||
|
||||
status |= (1 << bit_offset);
|
||||
return p_dev->write_status(reg_index, status);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_disable_QE(struct SFNOR_DEV *p_dev)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
int reg_index;
|
||||
int bit_offset;
|
||||
u8 status;
|
||||
|
||||
if (p_dev->manufacturer == MID_GIGADEV ||
|
||||
p_dev->manufacturer == MID_WINBOND) {
|
||||
reg_index = p_dev->QE_bits >> 3;
|
||||
bit_offset = p_dev->QE_bits & 0x7;
|
||||
ret = snor_read_status(reg_index, &status);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
|
||||
if (!(status & (1 << bit_offset)))
|
||||
return SFC_OK;
|
||||
|
||||
status &= ~(1 << bit_offset);
|
||||
return p_dev->write_status(reg_index, status);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if (SNOR_4BIT_DATA_DETECT_EN)
|
||||
static int snor_set_dlines(struct SFNOR_DEV *p_dev, enum SFC_DATA_LINES lines)
|
||||
{
|
||||
int ret;
|
||||
u8 read_cmd[] = {CMD_FAST_READ_X1, CMD_FAST_READ_X2, CMD_FAST_READ_X4};
|
||||
|
||||
if (lines == DATA_LINES_X4) {
|
||||
ret = snor_enable_QE(p_dev);
|
||||
if (ret != SFC_OK)
|
||||
return ret;
|
||||
}
|
||||
|
||||
p_dev->read_lines = lines;
|
||||
p_dev->read_cmd = read_cmd[lines];
|
||||
|
||||
if (p_dev->manufacturer == MID_GIGADEV ||
|
||||
p_dev->manufacturer == MID_WINBOND ||
|
||||
p_dev->manufacturer == MID_MACRONIX) {
|
||||
p_dev->prog_lines = (lines != DATA_LINES_X2) ?
|
||||
lines : DATA_LINES_X1;
|
||||
if (lines == DATA_LINES_X1) {
|
||||
p_dev->prog_cmd = CMD_PAGE_PROG;
|
||||
} else {
|
||||
if (p_dev->manufacturer == MID_GIGADEV ||
|
||||
p_dev->manufacturer == MID_WINBOND)
|
||||
p_dev->prog_cmd = CMD_PAGE_PROG_X4;
|
||||
else
|
||||
p_dev->prog_cmd = CMD_PAGE_PROG_A4;
|
||||
}
|
||||
}
|
||||
|
||||
return SFC_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
int snor_read_data(struct SFNOR_DEV *p_dev,
|
||||
u32 addr,
|
||||
void *p_data,
|
||||
u32 size)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
union SFCCTRL_DATA sfctrl;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = p_dev->read_cmd;
|
||||
sfcmd.b.datasize = size;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
|
||||
sfctrl.d32 = 0;
|
||||
sfctrl.b.datalines = p_dev->read_lines;
|
||||
if (!(size & 0x3) && size >= 4)
|
||||
sfctrl.b.enbledma = 0;
|
||||
|
||||
if (p_dev->read_cmd == CMD_FAST_READ_X1 ||
|
||||
p_dev->read_cmd == CMD_FAST_READ_X4 ||
|
||||
p_dev->read_cmd == CMD_FAST_READ_X2 ||
|
||||
p_dev->read_cmd == CMD_FAST_4READ_X4) {
|
||||
sfcmd.b.dummybits = 8;
|
||||
} else if (p_dev->read_cmd == CMD_FAST_READ_A4) {
|
||||
sfcmd.b.addrbits = SFC_ADDR_32BITS;
|
||||
addr = (addr << 8) | 0xFF; /* Set M[7:0] = 0xFF */
|
||||
sfcmd.b.dummybits = 4;
|
||||
sfctrl.b.addrlines = SFC_4BITS_LINE;
|
||||
}
|
||||
|
||||
if (p_dev->addr_mode == ADDR_MODE_4BYTE)
|
||||
sfcmd.b.addrbits = SFC_ADDR_32BITS;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, sfctrl.d32, addr, p_data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_read(struct SFNOR_DEV *p_dev, u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
u32 addr, size, len;
|
||||
u8 *p_buf = (u8 *)p_data;
|
||||
|
||||
if ((sec + n_sec) > p_dev->capacity)
|
||||
return SFC_PARAM_ERR;
|
||||
|
||||
mutex_lock(&p_dev->lock);
|
||||
addr = sec << 9;
|
||||
size = n_sec << 9;
|
||||
while (size) {
|
||||
len = size < SFC_MAX_IOSIZE ? size : SFC_MAX_IOSIZE;
|
||||
ret = snor_read_data(p_dev, addr, p_buf, len);
|
||||
if (ret != SFC_OK) {
|
||||
PRINT_SFC_E("snor_read_data %x ret= %x\n",
|
||||
addr >> 9, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
size -= len;
|
||||
addr += len;
|
||||
p_buf += len;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&p_dev->lock);
|
||||
if (!ret)
|
||||
ret = n_sec;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_write(struct SFNOR_DEV *p_dev, u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
int ret = SFC_OK;
|
||||
u32 len, blk_size, offset;
|
||||
u8 *p_buf = (u8 *)p_data;
|
||||
|
||||
if ((sec + n_sec) > p_dev->capacity)
|
||||
return SFC_PARAM_ERR;
|
||||
|
||||
mutex_lock(&p_dev->lock);
|
||||
while (n_sec) {
|
||||
if (sec < 512 || sec >= p_dev->capacity - 512)
|
||||
blk_size = 8;
|
||||
else
|
||||
blk_size = p_dev->blk_size;
|
||||
|
||||
offset = (sec & (blk_size - 1));
|
||||
if (!offset) {
|
||||
ret = snor_erase(p_dev, sec << 9, (blk_size == 8) ?
|
||||
ERASE_SECTOR : ERASE_BLOCK64K);
|
||||
if (ret != SFC_OK) {
|
||||
PRINT_SFC_E("snor_erase %x ret= %x\n",
|
||||
sec, ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
len = (blk_size - offset) < n_sec ?
|
||||
(blk_size - offset) : n_sec;
|
||||
ret = snor_prog(p_dev, sec << 9, p_buf, len << 9);
|
||||
if (ret != SFC_OK) {
|
||||
PRINT_SFC_E("snor_prog %x ret= %x\n", sec, ret);
|
||||
goto out;
|
||||
}
|
||||
n_sec -= len;
|
||||
sec += len;
|
||||
p_buf += len << 9;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&p_dev->lock);
|
||||
if (!ret)
|
||||
ret = n_sec;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int snor_read_id(u8 *data)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_READ_JEDECID;
|
||||
sfcmd.b.datasize = 3;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, 0, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snor_read_parameter(u32 addr, u8 *data)
|
||||
{
|
||||
int ret;
|
||||
union SFCCMD_DATA sfcmd;
|
||||
|
||||
sfcmd.d32 = 0;
|
||||
sfcmd.b.cmd = CMD_READ_PARAMETER;
|
||||
sfcmd.b.datasize = 1;
|
||||
sfcmd.b.addrbits = SFC_ADDR_24BITS;
|
||||
sfcmd.b.dummybits = 8;
|
||||
|
||||
ret = sfc_request(sfcmd.d32, 0, addr, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 snor_get_capacity(struct SFNOR_DEV *p_dev)
|
||||
{
|
||||
return p_dev->capacity;
|
||||
}
|
||||
|
||||
static void snor_print_spi_chip_info(struct SFNOR_DEV *p_dev)
|
||||
{
|
||||
PRINT_SFC_I("addr_mode: %x\n", p_dev->addr_mode);
|
||||
PRINT_SFC_I("read_lines: %x\n", p_dev->read_lines);
|
||||
PRINT_SFC_I("prog_lines: %x\n", p_dev->prog_lines);
|
||||
PRINT_SFC_I("read_cmd: %x\n", p_dev->read_cmd);
|
||||
PRINT_SFC_I("prog_cmd: %x\n", p_dev->prog_cmd);
|
||||
PRINT_SFC_I("blk_erase_cmd: %x\n", p_dev->blk_erase_cmd);
|
||||
PRINT_SFC_I("sec_erase_cmd: %x\n", p_dev->sec_erase_cmd);
|
||||
}
|
||||
|
||||
static struct flash_info *snor_get_flash_info(u8 *flash_id)
|
||||
{
|
||||
u32 i;
|
||||
u32 id = (flash_id[0] << 16) | (flash_id[1] << 8) | (flash_id[2] << 0);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(spi_flash_tbl); i++) {
|
||||
if (spi_flash_tbl[i].id == id)
|
||||
return &spi_flash_tbl[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Adjust flash info in ram base on parameter */
|
||||
static void *snor_flash_info_adjust(struct flash_info *spi_flash_info)
|
||||
{
|
||||
u32 addr;
|
||||
u8 para_version;
|
||||
|
||||
if (spi_flash_info->id == 0xc84019) {
|
||||
addr = 0x09;
|
||||
snor_read_parameter(addr, ¶_version);
|
||||
if (para_version == 0x06) {
|
||||
spi_flash_info->QE_bits = 9;
|
||||
spi_flash_info->prog_cmd_4 = 0x34;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int snor_init(struct SFNOR_DEV *p_dev)
|
||||
{
|
||||
u32 i;
|
||||
u8 id_byte[5];
|
||||
int err;
|
||||
|
||||
memset(p_dev, 0, sizeof(struct SFNOR_DEV));
|
||||
snor_read_id(id_byte);
|
||||
PRINT_SFC_E("sfc nor id: %x %x %x\n",
|
||||
id_byte[0], id_byte[1], id_byte[2]);
|
||||
if (0xFF == id_byte[0] || 0x00 == id_byte[0]) {
|
||||
err = SFC_ERROR;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
p_dev->manufacturer = id_byte[0];
|
||||
p_dev->mem_type = id_byte[1];
|
||||
|
||||
mutex_init(&p_dev->lock);
|
||||
g_spi_flash_info = snor_get_flash_info(id_byte);
|
||||
if (g_spi_flash_info) {
|
||||
snor_flash_info_adjust(g_spi_flash_info);
|
||||
p_dev->capacity = 1 << g_spi_flash_info->density;
|
||||
p_dev->blk_size = g_spi_flash_info->block_size;
|
||||
p_dev->page_size = NOR_SECS_PAGE;
|
||||
p_dev->read_cmd = g_spi_flash_info->read_cmd;
|
||||
p_dev->prog_cmd = g_spi_flash_info->prog_cmd;
|
||||
p_dev->sec_erase_cmd = g_spi_flash_info->sector_erase_cmd;
|
||||
p_dev->blk_erase_cmd = g_spi_flash_info->block_erase_cmd;
|
||||
p_dev->prog_lines = DATA_LINES_X1;
|
||||
p_dev->read_lines = DATA_LINES_X1;
|
||||
p_dev->QE_bits = g_spi_flash_info->QE_bits;
|
||||
|
||||
i = g_spi_flash_info->feature & FEA_READ_STATUE_MASK;
|
||||
if (i == 0)
|
||||
p_dev->write_status = snor_write_status;
|
||||
else
|
||||
p_dev->write_status = snor_write_status2;
|
||||
if (g_spi_flash_info->feature & FEA_4BIT_READ) {
|
||||
if (snor_enable_QE(p_dev) == SFC_OK) {
|
||||
p_dev->read_lines = DATA_LINES_X4;
|
||||
p_dev->read_cmd = g_spi_flash_info->read_cmd_4;
|
||||
}
|
||||
}
|
||||
if (g_spi_flash_info->feature & FEA_4BIT_PROG &&
|
||||
p_dev->read_lines == DATA_LINES_X4) {
|
||||
p_dev->prog_lines = DATA_LINES_X4;
|
||||
p_dev->prog_cmd = g_spi_flash_info->prog_cmd_4;
|
||||
}
|
||||
|
||||
if (g_spi_flash_info->feature & FEA_4BYTE_ADDR)
|
||||
p_dev->addr_mode = ADDR_MODE_4BYTE;
|
||||
|
||||
if ((g_spi_flash_info->feature & FEA_4BYTE_ADDR_MODE))
|
||||
snor_enter_4byte_mode();
|
||||
#ifdef CONFIG_RK_SFC_NOR_MTD
|
||||
err = sfc_nor_mtd_init(p_dev);
|
||||
if (err)
|
||||
goto err_out;
|
||||
#endif
|
||||
|
||||
goto normal_out;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(sfnor_dev_code); i++) {
|
||||
if (id_byte[2] == sfnor_dev_code[i]) {
|
||||
p_dev->capacity = sfnor_capacity[i] >> 9;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= sizeof(sfnor_dev_code)) {
|
||||
err = SFC_ERROR;
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
p_dev->QE_bits = 9;
|
||||
p_dev->blk_size = NOR_SECS_BLK;
|
||||
p_dev->page_size = NOR_SECS_PAGE;
|
||||
p_dev->read_cmd = CMD_READ_DATA;
|
||||
p_dev->prog_cmd = CMD_PAGE_PROG;
|
||||
p_dev->sec_erase_cmd = CMD_SECTOR_ERASE;
|
||||
p_dev->blk_erase_cmd = CMD_BLOCK_ERASE;
|
||||
p_dev->write_status = snor_write_status2;
|
||||
#if (SNOR_4BIT_DATA_DETECT_EN)
|
||||
snor_set_dlines(p_dev, DATA_LINES_X4);
|
||||
#endif
|
||||
|
||||
normal_out:
|
||||
snor_print_spi_chip_info(p_dev);
|
||||
|
||||
return SFC_OK;
|
||||
|
||||
err_out:
|
||||
return err;
|
||||
}
|
||||
|
||||
171
drivers/rkflash/sfc_nor.h
Normal file
171
drivers/rkflash/sfc_nor.h
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _SFNOR_H
|
||||
#define _SFNOR_H
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
|
||||
#include "sfc.h"
|
||||
|
||||
/* Four line data transmission detection */
|
||||
#define SNOR_4BIT_DATA_DETECT_EN 0
|
||||
|
||||
#define NOR_PAGE_SIZE 256
|
||||
#define NOR_BLOCK_SIZE (64 * 1024)
|
||||
#define NOR_SECS_BLK (NOR_BLOCK_SIZE / 512)
|
||||
#define NOR_SECS_PAGE 4
|
||||
|
||||
#define FEA_READ_STATUE_MASK (0x3 << 0)
|
||||
#define FEA_STATUE_MODE1 0
|
||||
#define FEA_STATUE_MODE2 1
|
||||
#define FEA_4BIT_READ BIT(2)
|
||||
#define FEA_4BIT_PROG BIT(3)
|
||||
#define FEA_4BYTE_ADDR BIT(4)
|
||||
#define FEA_4BYTE_ADDR_MODE BIT(5)
|
||||
|
||||
/*Manufactory ID*/
|
||||
#define MID_WINBOND 0xEF
|
||||
#define MID_GIGADEV 0xC8
|
||||
#define MID_MICRON 0x2C
|
||||
#define MID_MACRONIX 0xC2
|
||||
#define MID_SPANSION 0x01
|
||||
#define MID_EON 0x1C
|
||||
#define MID_ST 0x20
|
||||
|
||||
/*Command Set*/
|
||||
#define CMD_READ_JEDECID (0x9F)
|
||||
#define CMD_READ_DATA (0x03)
|
||||
#define CMD_READ_STATUS (0x05)
|
||||
#define CMD_WRITE_STATUS (0x01)
|
||||
#define CMD_PAGE_PROG (0x02)
|
||||
#define CMD_SECTOR_ERASE (0x20)
|
||||
#define CMD_BLK64K_ERASE (0xD8)
|
||||
#define CMD_BLK32K_ERASE (0x52)
|
||||
#define CMD_CHIP_ERASE (0xC7)
|
||||
#define CMD_WRITE_EN (0x06)
|
||||
#define CMD_WRITE_DIS (0x04)
|
||||
#define CMD_PAGE_READ (0x13)
|
||||
#define CMD_GET_FEATURE (0x0F)
|
||||
#define CMD_SET_FEATURE (0x1F)
|
||||
#define CMD_PROG_LOAD (0x02)
|
||||
#define CMD_PROG_EXEC (0x10)
|
||||
#define CMD_BLOCK_ERASE (0xD8)
|
||||
#define CMD_READ_DATA_X2 (0x3B)
|
||||
#define CMD_READ_DATA_X4 (0x6B)
|
||||
#define CMD_PROG_LOAD_X4 (0x32)
|
||||
#define CMD_READ_STATUS2 (0x35)
|
||||
#define CMD_READ_STATUS3 (0x15)
|
||||
#define CMD_WRITE_STATUS2 (0x31)
|
||||
#define CMD_WRITE_STATUS3 (0x11)
|
||||
/* X1 cmd, X1 addr, X1 data */
|
||||
#define CMD_FAST_READ_X1 (0x0B)
|
||||
/* X1 cmd, X1 addr, X2 data */
|
||||
#define CMD_FAST_READ_X2 (0x3B)
|
||||
/* X1 cmd, X1 addr, X4 data SUPPORT GD MARCONIX WINBOND */
|
||||
#define CMD_FAST_READ_X4 (0x6B)
|
||||
/* X1 cmd, X1 addr, X4 data SUPPORT GD MARCONIX WINBOND */
|
||||
#define CMD_FAST_4READ_X4 (0x6C)
|
||||
/* X1 cmd, X4 addr, X4 data SUPPORT EON GD MARCONIX WINBOND */
|
||||
#define CMD_FAST_READ_A4 (0xEB)
|
||||
/* X1 cmd, X1 addr, X4 data, SUPPORT GD WINBOND */
|
||||
#define CMD_PAGE_PROG_X4 (0x32)
|
||||
/* X1 cmd, X4 addr, X4 data, SUPPORT MARCONIX */
|
||||
#define CMD_PAGE_PROG_A4 (0x38)
|
||||
#define CMD_RESET_NAND (0xFF)
|
||||
#define CMD_ENTER_4BYTE_MODE (0xB7)
|
||||
#define CMD_EXIT_4BYTE_MODE (0xE9)
|
||||
#define CMD_ENABLE_RESER (0x66)
|
||||
#define CMD_RESET_DEVICE (0x99)
|
||||
#define CMD_READ_PARAMETER (0x5A)
|
||||
|
||||
enum NOR_ERASE_TYPE {
|
||||
ERASE_SECTOR = 0,
|
||||
ERASE_BLOCK64K,
|
||||
ERASE_CHIP
|
||||
};
|
||||
|
||||
enum SNOR_IO_MODE {
|
||||
IO_MODE_SPI = 0,
|
||||
IO_MODE_QPI
|
||||
};
|
||||
|
||||
enum SNOR_READ_MODE {
|
||||
READ_MODE_NOMAL = 0,
|
||||
READ_MODE_FAST
|
||||
};
|
||||
|
||||
enum SNOR_ADDR_MODE {
|
||||
ADDR_MODE_3BYTE = 0,
|
||||
ADDR_MODE_4BYTE
|
||||
};
|
||||
|
||||
typedef int (*SNOR_WRITE_STATUS)(u32 reg_index, u8 status);
|
||||
|
||||
struct SFNOR_DEV {
|
||||
u32 capacity;
|
||||
u8 manufacturer;
|
||||
u8 mem_type;
|
||||
u16 page_size;
|
||||
u32 blk_size;
|
||||
|
||||
u8 read_cmd;
|
||||
u8 prog_cmd;
|
||||
u8 sec_erase_cmd;
|
||||
u8 blk_erase_cmd;
|
||||
u8 QE_bits;
|
||||
|
||||
enum SNOR_READ_MODE read_mode;
|
||||
enum SNOR_ADDR_MODE addr_mode;
|
||||
enum SNOR_IO_MODE io_mode;
|
||||
|
||||
enum SFC_DATA_LINES read_lines;
|
||||
enum SFC_DATA_LINES prog_lines;
|
||||
|
||||
SNOR_WRITE_STATUS write_status;
|
||||
struct mutex lock; /* to lock this object */
|
||||
#ifdef CONFIG_RK_SFC_NOR_MTD
|
||||
struct mtd_info mtd;
|
||||
u8 *dma_buf;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct flash_info {
|
||||
u32 id;
|
||||
|
||||
u8 block_size;
|
||||
u8 sector_size;
|
||||
u8 read_cmd;
|
||||
u8 prog_cmd;
|
||||
|
||||
u8 read_cmd_4;
|
||||
u8 prog_cmd_4;
|
||||
u8 sector_erase_cmd;
|
||||
u8 block_erase_cmd;
|
||||
|
||||
u8 feature;
|
||||
u8 density; /* (1 << density) sectors*/
|
||||
u8 QE_bits;
|
||||
u8 reserved2;
|
||||
};
|
||||
|
||||
int snor_init(struct SFNOR_DEV *p_dev);
|
||||
u32 snor_get_capacity(struct SFNOR_DEV *p_dev);
|
||||
int snor_read(struct SFNOR_DEV *p_dev, u32 sec, u32 n_sec, void *p_data);
|
||||
int snor_write(struct SFNOR_DEV *p_dev, u32 sec, u32 n_sec, void *p_data);
|
||||
int snor_erase(struct SFNOR_DEV *p_dev,
|
||||
u32 addr,
|
||||
enum NOR_ERASE_TYPE erase_type);
|
||||
int snor_read_id(u8 *data);
|
||||
int snor_prog_page(struct SFNOR_DEV *p_dev, u32 addr, void *p_data, u32 size);
|
||||
int snor_read_data(struct SFNOR_DEV *p_dev, u32 addr, void *p_data, u32 size);
|
||||
int snor_reset_device(void);
|
||||
int snor_disable_QE(struct SFNOR_DEV *p_dev);
|
||||
extern struct flash_info *g_spi_flash_info;
|
||||
extern struct SFNOR_DEV sfnor_dev;
|
||||
|
||||
int sfc_nor_mtd_init(struct SFNOR_DEV *p_dev);
|
||||
|
||||
#endif
|
||||
110
drivers/rkflash/sfc_nor_boot.c
Normal file
110
drivers/rkflash/sfc_nor_boot.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include "sfc.h"
|
||||
#include "sfc_nor.h"
|
||||
#include "sfc_nor_boot.h"
|
||||
#include "typedef.h"
|
||||
|
||||
#define VENDOR_PART_NUM 4
|
||||
|
||||
#define FLASH_VENDOR_PART_START 8
|
||||
#define FLASH_VENDOR_PART_SIZE 8
|
||||
#define FLASH_VENDOR_ITEM_NUM 62
|
||||
#define FLASH_VENDOR_PART_END \
|
||||
(FLASH_VENDOR_PART_START +\
|
||||
FLASH_VENDOR_PART_SIZE * VENDOR_PART_NUM - 1)
|
||||
|
||||
/* SFNOR_DEV sfnor_dev is in the sfc_nor.h */
|
||||
int spi_flash_init(void __iomem *reg_addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
sfc_init(reg_addr);
|
||||
ret = snor_init(&sfnor_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_flash_init);
|
||||
|
||||
void spi_flash_read_id(u8 chip_sel, void *buf)
|
||||
{
|
||||
snor_read_id(buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_flash_read_id);
|
||||
|
||||
int snor_read_lba(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 count, offset;
|
||||
char *buf;
|
||||
|
||||
if (sec + n_sec - 1 < FLASH_VENDOR_PART_START ||
|
||||
sec > FLASH_VENDOR_PART_END) {
|
||||
ret = snor_read(&sfnor_dev, sec, n_sec, p_data);
|
||||
} else {
|
||||
memset(p_data, 0, 512 * n_sec);
|
||||
if (sec < FLASH_VENDOR_PART_START) {
|
||||
count = FLASH_VENDOR_PART_START - sec;
|
||||
buf = p_data;
|
||||
ret = snor_read(&sfnor_dev, sec, count, buf);
|
||||
}
|
||||
if ((sec + n_sec - 1) > FLASH_VENDOR_PART_END) {
|
||||
count = sec + n_sec - 1 - FLASH_VENDOR_PART_END;
|
||||
offset = FLASH_VENDOR_PART_END - sec + 1;
|
||||
buf = p_data + offset * 512;
|
||||
ret = snor_read(&sfnor_dev,
|
||||
FLASH_VENDOR_PART_END + 1,
|
||||
count, buf);
|
||||
}
|
||||
}
|
||||
|
||||
return (u32)ret == n_sec ? 0 : ret;
|
||||
}
|
||||
|
||||
int snor_write_lba(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = snor_write(&sfnor_dev, sec, n_sec, p_data);
|
||||
|
||||
return (u32)ret == n_sec ? 0 : ret;
|
||||
}
|
||||
|
||||
int snor_vendor_read(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = snor_read(&sfnor_dev, sec, n_sec, p_data);
|
||||
|
||||
return (u32)ret == n_sec ? 0 : ret;
|
||||
}
|
||||
|
||||
int snor_vendor_write(u32 sec, u32 n_sec, void *p_data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = snor_write(&sfnor_dev, sec, n_sec, p_data);
|
||||
|
||||
return (u32)ret == n_sec ? 0 : ret;
|
||||
}
|
||||
|
||||
unsigned int snor_capacity(void)
|
||||
{
|
||||
return snor_get_capacity(&sfnor_dev);
|
||||
}
|
||||
|
||||
void snor_deinit(void)
|
||||
{
|
||||
snor_disable_QE(&sfnor_dev);
|
||||
snor_reset_device();
|
||||
}
|
||||
|
||||
int snor_resume(void __iomem *reg_addr)
|
||||
{
|
||||
return snor_init(&sfnor_dev);
|
||||
}
|
||||
19
drivers/rkflash/sfc_nor_boot.h
Normal file
19
drivers/rkflash/sfc_nor_boot.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef _SFC_NOR_BOOT_H
|
||||
#define _SFC_NOR_BOOT_H
|
||||
|
||||
int spi_flash_init(void __iomem *reg_addr);
|
||||
void spi_flash_read_id(u8 chip_sel, void *buf);
|
||||
int snor_read_lba(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
int snor_write_lba(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
unsigned int snor_capacity(void);
|
||||
void snor_deinit(void);
|
||||
int snor_resume(void __iomem *reg_addr);
|
||||
int snor_vendor_read(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
int snor_vendor_write(unsigned int sec, unsigned int n_sec, void *p_data);
|
||||
|
||||
#endif
|
||||
|
||||
224
drivers/rkflash/sfc_nor_mtd.c
Normal file
224
drivers/rkflash/sfc_nor_mtd.c
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/cfi.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "sfc_nor.h"
|
||||
#include "rkflash_blk.h"
|
||||
#include "rkflash_debug.h"
|
||||
|
||||
static struct mtd_partition nor_parts[MAX_PART_COUNT];
|
||||
|
||||
static inline struct SFNOR_DEV *mtd_to_sfc(struct mtd_info *ptr_mtd)
|
||||
{
|
||||
return (struct SFNOR_DEV *)((char *)ptr_mtd -
|
||||
offsetof(struct SFNOR_DEV, mtd));
|
||||
}
|
||||
|
||||
static int sfc_erase_mtd(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
int ret;
|
||||
struct SFNOR_DEV *p_dev = mtd_to_sfc(mtd);
|
||||
u32 addr, len;
|
||||
u32 rem;
|
||||
|
||||
if ((instr->addr + instr->len) > p_dev->capacity << 9)
|
||||
return -EINVAL;
|
||||
|
||||
div_u64_rem(instr->len, mtd->erasesize, &rem);
|
||||
if (rem)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&p_dev->lock);
|
||||
|
||||
addr = instr->addr;
|
||||
len = instr->len;
|
||||
|
||||
if (len == p_dev->mtd.size) {
|
||||
ret = snor_erase(p_dev, 0, CMD_CHIP_ERASE);
|
||||
if (ret) {
|
||||
PRINT_SFC_E("snor_erase CHIP 0x%x ret=%d\n",
|
||||
addr, ret);
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
mutex_unlock(&p_dev->lock);
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
while (len > 0) {
|
||||
ret = snor_erase(p_dev, addr, ERASE_BLOCK64K);
|
||||
if (ret) {
|
||||
PRINT_SFC_E("snor_erase 0x%x ret=%d\n",
|
||||
addr, ret);
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
mutex_unlock(&p_dev->lock);
|
||||
return -EIO;
|
||||
}
|
||||
addr += mtd->erasesize;
|
||||
len -= mtd->erasesize;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&p_dev->lock);
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sfc_write_mtd(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
int status;
|
||||
u32 addr, size, chunk, padding;
|
||||
u32 page_align;
|
||||
struct SFNOR_DEV *p_dev = mtd_to_sfc(mtd);
|
||||
|
||||
if ((to + len) > p_dev->capacity << 9)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&p_dev->lock);
|
||||
|
||||
addr = to;
|
||||
size = len;
|
||||
|
||||
while (size > 0) {
|
||||
page_align = addr & (NOR_PAGE_SIZE - 1);
|
||||
chunk = size;
|
||||
if (chunk > (NOR_PAGE_SIZE - page_align))
|
||||
chunk = NOR_PAGE_SIZE - page_align;
|
||||
memcpy(p_dev->dma_buf, buf, chunk);
|
||||
padding = 0;
|
||||
if (chunk < NOR_PAGE_SIZE) {
|
||||
/* 4 bytes algin */
|
||||
padding = ((chunk + 3) & 0xFFFC) - chunk;
|
||||
memset(p_dev->dma_buf + chunk, 0xFF, padding);
|
||||
}
|
||||
status = snor_prog_page(p_dev, addr, p_dev->dma_buf,
|
||||
chunk + padding);
|
||||
if (status != SFC_OK) {
|
||||
PRINT_SFC_E("snor_prog_page %x ret= %d\n",
|
||||
addr, status);
|
||||
*retlen = len - size;
|
||||
mutex_unlock(&p_dev->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
size -= chunk;
|
||||
addr += chunk;
|
||||
buf += chunk;
|
||||
}
|
||||
*retlen = len;
|
||||
mutex_unlock(&p_dev->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sfc_read_mtd(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
u32 addr, size, chunk;
|
||||
u8 *p_buf = (u8 *)buf;
|
||||
int ret = SFC_OK;
|
||||
|
||||
struct SFNOR_DEV *p_dev = mtd_to_sfc(mtd);
|
||||
|
||||
if ((from + len) > p_dev->capacity << 9)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&p_dev->lock);
|
||||
|
||||
addr = from;
|
||||
size = len;
|
||||
|
||||
while (size > 0) {
|
||||
chunk = (size < NOR_PAGE_SIZE) ? size : NOR_PAGE_SIZE;
|
||||
ret = snor_read_data(p_dev, addr, p_dev->dma_buf, chunk);
|
||||
if (ret != SFC_OK) {
|
||||
PRINT_SFC_E("snor_read_data %x ret=%d\n", addr, ret);
|
||||
*retlen = len - size;
|
||||
mutex_unlock(&p_dev->lock);
|
||||
return ret;
|
||||
}
|
||||
memcpy(p_buf, p_dev->dma_buf, chunk);
|
||||
size -= chunk;
|
||||
addr += chunk;
|
||||
p_buf += chunk;
|
||||
}
|
||||
|
||||
*retlen = len;
|
||||
mutex_unlock(&p_dev->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sfc_nor_mtd_init(struct SFNOR_DEV *p_dev)
|
||||
{
|
||||
int ret, i, part_num = 0;
|
||||
int capacity;
|
||||
struct STRUCT_PART_INFO *g_part; /* size 2KB */
|
||||
|
||||
capacity = p_dev->capacity;
|
||||
p_dev->mtd.name = "sfc_nor";
|
||||
p_dev->mtd.type = MTD_NORFLASH;
|
||||
p_dev->mtd.writesize = 1;
|
||||
p_dev->mtd.flags = MTD_CAP_NORFLASH;
|
||||
/* see snor_write */
|
||||
p_dev->mtd.size = capacity << 9;
|
||||
p_dev->mtd._erase = sfc_erase_mtd;
|
||||
p_dev->mtd._read = sfc_read_mtd;
|
||||
p_dev->mtd._write = sfc_write_mtd;
|
||||
p_dev->mtd.erasesize = g_spi_flash_info->block_size << 9;
|
||||
p_dev->mtd.writebufsize = NOR_PAGE_SIZE;
|
||||
|
||||
p_dev->dma_buf = kmalloc(NOR_PAGE_SIZE, GFP_KERNEL | GFP_DMA);
|
||||
if (!p_dev->dma_buf) {
|
||||
PRINT_SFC_E("kmalloc size=0x%x failed\n", NOR_PAGE_SIZE);
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_part = kmalloc(sizeof(*g_part), GFP_KERNEL | GFP_DMA);
|
||||
if (!g_part) {
|
||||
ret = -ENOMEM;
|
||||
goto free_dma_buf;
|
||||
}
|
||||
part_num = 0;
|
||||
if (snor_read(p_dev, 0, 4, g_part) == 0) {
|
||||
if (g_part->hdr.ui_fw_tag == RK_PARTITION_TAG) {
|
||||
part_num = g_part->hdr.ui_part_entry_count;
|
||||
for (i = 0; i < part_num; i++) {
|
||||
nor_parts[i].name =
|
||||
kstrdup(g_part->part[i].sz_name,
|
||||
GFP_KERNEL);
|
||||
if (g_part->part[i].ui_pt_sz == 0xFFFFFFFF)
|
||||
g_part->part[i].ui_pt_sz = capacity -
|
||||
g_part->part[i].ui_pt_off;
|
||||
nor_parts[i].offset =
|
||||
(u64)g_part->part[i].ui_pt_off << 9;
|
||||
nor_parts[i].size =
|
||||
(u64)g_part->part[i].ui_pt_sz << 9;
|
||||
nor_parts[i].mask_flags = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree(g_part);
|
||||
if (part_num == 0) {
|
||||
ret = -1;
|
||||
goto free_dma_buf;
|
||||
}
|
||||
ret = mtd_device_register(&p_dev->mtd, nor_parts, part_num);
|
||||
if (ret != 0)
|
||||
goto free_dma_buf;
|
||||
return ret;
|
||||
|
||||
free_dma_buf:
|
||||
kfree(p_dev->dma_buf);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
41
drivers/rkflash/typedef.h
Normal file
41
drivers/rkflash/typedef.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Copyright (c) 2018 Rockchip Electronics Co. Ltd. */
|
||||
|
||||
#ifndef __TYPE_DEF_H
|
||||
#define __TYPE_DEF_H
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL 0
|
||||
#endif
|
||||
|
||||
#define OK 0
|
||||
#define ERROR (-1)
|
||||
|
||||
#define FTL_ERROR ERROR
|
||||
#define FTL_OK OK
|
||||
#define FTL_NO_FLASH -2
|
||||
#define FTL_NO_IDB -3
|
||||
#define FTL_UNSUPPORTED_FLASH -4
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE (!FALSE)
|
||||
|
||||
#define INVALID_UINT8 ((u8)0xFF)
|
||||
#define INVALID_UINT16 ((u16)0xFFFF)
|
||||
#define INVALID_UINT32 ((u32)0xFFFFFFFFL)
|
||||
|
||||
#define PRINT_E pr_info
|
||||
#define PRINT_I pr_info
|
||||
|
||||
void *ftl_malloc(int n_size);
|
||||
void *ftl_memset(void *s, int c, unsigned int n);
|
||||
void *ftl_memcpy(void *pv_to,
|
||||
const void *pv_from,
|
||||
unsigned int size);
|
||||
void ftl_free(void *p, int size);
|
||||
void rknand_print_hex(char *s, void *buf, int width, int len);
|
||||
|
||||
#endif /*__TYPEDEF_H */
|
||||
Loading…
Reference in New Issue
Block a user