blob: ef916158a33345cbf931c149e29e38f914cb97dc [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <asm/div64.h>
#include <linux/sizes.h>
#include <linux/of.h>
#include <linux/version.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/spinand.h>
#include <linux/pinctrl/consumer.h>
#include <linux/amlogic/aml_rsv.h>
#include <linux/amlogic/aml_spi_nand.h>
#define NAND_BLOCK_GOOD 0
#define NAND_BLOCK_BAD 1
#define NAND_FACTORY_BAD 2
#define NAND_FIPMODE_DISCRETE (1)
//#define CONFIG_NOT_SKIP_BAD_BLOCK
struct meson_spinand {
struct mtd_info *mtd;
struct spinand_device *spinand;
struct meson_rsv_handler_t *rsv;
s8 *block_status;
unsigned int erasesize_shift;
};
struct meson_spinand *meson_spinand_global;
static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
{
struct meson_spinand *meson_spinand = meson_spinand_global;
struct spinand_device *spinand = meson_spinand->spinand;
struct nand_device *nand = mtd_to_nanddev(mtd);
struct nand_pos pos;
u8 bad_block;
nanddev_offs_to_pos(nand, offs, &pos);
mutex_lock(&spinand->lock);
if (meson_spinand->block_status) {
/* TODO: Keep one plane */
bad_block = meson_spinand->block_status[pos.eraseblock];
if (bad_block != NAND_BLOCK_BAD &&
bad_block != NAND_FACTORY_BAD &&
bad_block != NAND_BLOCK_GOOD) {
pr_err("bad block table is mixed\n");
mutex_unlock(&spinand->lock);
return true;
}
if (bad_block == NAND_BLOCK_GOOD) {
mutex_unlock(&spinand->lock);
return false;
}
pr_info("%s bad block at 0x%x\n",
(bad_block == NAND_FACTORY_BAD) ? "factory" : "user",
(u32)offs);
mutex_unlock(&spinand->lock);
return true;
}
pr_info("bbt table is not initial");
mutex_unlock(&spinand->lock);
return false;
}
static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
{
struct meson_spinand *meson_spinand = meson_spinand_global;
struct spinand_device *spinand = meson_spinand->spinand;
struct nand_device *nand = mtd_to_nanddev(mtd);
struct nand_pos pos;
u8 bad_block;
s8 *buf = NULL;
nanddev_offs_to_pos(nand, offs, &pos);
mutex_lock(&spinand->lock);
if (meson_spinand->block_status) {
/* TODO: Keep one plane */
bad_block = meson_spinand->block_status[pos.eraseblock];
if (bad_block != NAND_BLOCK_BAD &&
bad_block != NAND_FACTORY_BAD &&
bad_block != NAND_BLOCK_GOOD) {
pr_err("bad block table is mixed\n");
mutex_unlock(&spinand->lock);
return -EINVAL;
}
if (bad_block == NAND_BLOCK_GOOD) {
buf = meson_spinand->block_status;
buf[pos.eraseblock] = NAND_BLOCK_BAD;
meson_rsv_bbt_write((u_char *)buf,
meson_spinand->rsv->bbt->size);
}
mutex_unlock(&spinand->lock);
return 0;
}
pr_info("bbt table is not initial");
mutex_unlock(&spinand->lock);
return -EINVAL;
}
int meson_spinand_bbt_check(struct mtd_info *mtd)
{
struct meson_spinand *meson_spinand = meson_spinand_global;
int ret;
ret = meson_rsv_scan(meson_spinand->rsv->bbt);
if (ret != 0 && (ret != (-1)))
return ret;
if (meson_spinand->rsv->bbt->valid == 1) {
pr_info("%s %d bbt is valid, reading.\n", __func__, __LINE__);
meson_rsv_read(meson_spinand->rsv->bbt,
(u_char *)meson_spinand->block_status);
}
return 0;
}
int meson_spinand_init(struct spinand_device *spinand, struct mtd_info *mtd)
{
struct meson_spinand *meson_spinand = NULL;
int err = 0;
meson_spinand = kzalloc(sizeof(*meson_spinand), GFP_KERNEL);
if (!meson_spinand)
return -ENOMEM;
meson_spinand_global = meson_spinand;
meson_spinand->erasesize_shift = ffs(mtd->erasesize) - 1;
meson_spinand->block_status =
kzalloc((mtd->size >> meson_spinand->erasesize_shift),
GFP_KERNEL);
if (!meson_spinand->block_status) {
err = -ENOMEM;
goto exit_error2;
}
meson_spinand->mtd = mtd;
meson_spinand->spinand = spinand;
meson_spinand->rsv = kzalloc(sizeof(*meson_spinand->rsv),
GFP_KERNEL);
if (!meson_spinand->rsv) {
err = -ENOMEM;
goto exit_error1;
}
mtd->_block_isbad = spinand_mtd_block_isbad;
mtd->_block_markbad = spinand_mtd_block_markbad;
mtd->_block_isreserved = NULL;
mtd->erasesize_shift = meson_spinand->erasesize_shift;
mtd->writesize_shift = ffs(mtd->writesize) - 1;
meson_rsv_init(mtd, meson_spinand->rsv);
err = meson_spinand_bbt_check(mtd);
if (err) {
pr_err("Couldn't search or uncorrected bad block table\n");
err = -ENODEV;
goto exit_error;
}
#ifndef CONFIG_MTD_ENV_IN_NAND
meson_rsv_check(meson_spinand->rsv->env);
#endif
meson_rsv_check(meson_spinand->rsv->key);
meson_rsv_check(meson_spinand->rsv->dtb);
return 0;
exit_error:
kfree(meson_spinand->rsv);
exit_error1:
kfree(meson_spinand->block_status);
exit_error2:
kfree(meson_spinand);
return err;
}
EXPORT_SYMBOL_GPL(meson_spinand_init);
bool spinand_is_info_page(struct nand_device *nand, int page)
{
return unlikely((page % 128) == (SPI_NAND_BL2_PAGES - 1) &&
page < SPI_NAND_BOOT_TOTAL_PAGES);
}
EXPORT_SYMBOL_GPL(spinand_is_info_page);
int spinand_set_info_page(struct mtd_info *mtd, void *buf)
{
struct meson_spinand *spinand = meson_spinand_global;
u32 page_per_blk;
struct mtd_oob_region region;
struct spinand_info_page *info_page = (struct spinand_info_page *)buf;
page_per_blk = mtd->erasesize / mtd->writesize;
memcpy(info_page->magic, SPINAND_MAGIC, strlen(SPINAND_MAGIC));
info_page->version = SPINAND_INFO_VER;
/* DISCRETE only */
info_page->mode = 1;
info_page->bl2_num = SPI_NAND_BL2_COPY_NUM;
info_page->fip_num = SPI_NAND_TPL_COPY_NUM;
info_page->dev.s.rd_max = SPI_NAND_NBITS;
info_page->dev.s.fip_start =
SPI_NAND_BOOT_TOTAL_PAGES + NAND_RSV_BLOCK_NUM * page_per_blk;
info_page->dev.s.fip_pages = SPI_NAND_TPL_SIZE_PER_COPY / mtd->writesize;
info_page->dev.s.page_size = mtd->writesize;
info_page->dev.s.page_per_blk = page_per_blk;
info_page->dev.s.oob_size = mtd->oobsize;
mtd->ooblayout->free(mtd, 0, &region);
info_page->dev.s.oob_offset = region.offset;
info_page->dev.s.bbt_start = 0;
info_page->dev.s.bbt_valid = 0;
info_page->dev.s.bbt_size = spinand->rsv->bbt->size;
return 0;
}
EXPORT_SYMBOL_GPL(spinand_set_info_page);
struct meson_partition_platform_data {
u32 reserved_part_blk_num;
u32 bl_mode;
u32 fip_copies;
u32 fip_size;
struct mtd_partition *part;
u32 part_num;
};
static struct meson_partition_platform_data *
meson_partition_parse_platform_data(struct device_node *np)
{
struct meson_partition_platform_data *pdata = NULL;
struct device_node *part_np, *child;
struct mtd_partition *part;
phandle phandles;
int part_num, ret;
if (!np)
return NULL;
ret = of_property_read_u32(np, "partition", (u32 *)&phandles);
if (ret) {
pr_info("%s: no partition in dts\n", __func__);
return NULL;
}
part_np = of_find_node_by_phandle(phandles);
if (!part_np) {
pr_info("%s: partition handle error\n", __func__);
return NULL;
}
child = of_get_next_child(part_np, NULL);
part_num = of_get_child_count(part_np);
if (!child || !part_num) {
pr_info("%s: no partition table in dts\n", __func__);
return NULL;
}
pdata = kzalloc(sizeof(*pdata) + sizeof(*part) * part_num,
GFP_KERNEL);
pdata->part = (struct mtd_partition *)&pdata[1];
pdata->part_num = part_num;
ret = of_property_read_u32(np, "bl_mode", &pdata->bl_mode);
pr_info("bl_mode %s\n", pdata->bl_mode ? "descrete" : "compact");
ret = of_property_read_u32(np, "fip_copies", &pdata->fip_copies);
pr_info("fip_copies %d\n", pdata->fip_copies);
ret = of_property_read_u32(np, "fip_size", &pdata->fip_size);
pr_info("fip_size 0x%x\n", pdata->fip_size);
part = pdata->part;
for_each_child_of_node(part_np, child) {
part->name = (char *)child->name;
if (of_property_read_u64(child, "offset", &part->offset))
goto parse_err;
if (of_property_read_u64(child, "size", &part->size))
goto parse_err;
part++;
}
return pdata;
parse_err:
kfree(pdata);
return NULL;
}
static void meson_partition_relocate(struct mtd_info *mtd,
struct mtd_partition *part)
{
#ifndef CONFIG_NOT_SKIP_BAD_BLOCK
loff_t offset = part->offset;
loff_t end = offset + part->size;
while (offset < end) {
if (mtd->_block_isbad(mtd, offset)) {
part->size += mtd->erasesize;
end += mtd->erasesize;
if (end > mtd->size)
break;
}
offset += mtd->erasesize;
}
#endif
}
int meson_add_mtd_partitions(struct mtd_info *mtd)
{
//struct nand_device *nand = mtd_to_nanddev(mtd);
struct meson_partition_platform_data *pdata;
struct mtd_partition *part;
loff_t offset;
int i;
pdata = meson_partition_parse_platform_data(mtd_get_of_node(mtd));
if (!pdata) {
pr_err("%s: parse platform data failed\n", __func__);
return -ENODEV;
}
/* bootloader */
part = pdata->part;
offset = 0;
part->offset = offset;
part->size = SPI_NAND_BOOT_TOTAL_PAGES * mtd->writesize;
offset += part->size;
/* skip rsv */
offset += NAND_RSV_BLOCK_NUM * mtd->erasesize;
/* tpl, support NAND_FIPMODE_DISCRETE only */
part++;
part->offset = offset;
part->size = pdata->fip_copies * pdata->fip_size;
offset += part->size;
i = pdata->part_num - 3;
while (i--) {
part++;
part->offset = offset;
meson_partition_relocate(mtd, part);
offset += part->size;
if (offset > mtd->size)
goto meson_add_mtd_partitions_err;
}
/* data */
part++;
part->offset = offset;
part->size = mtd->size - offset;
return mtd_device_register(mtd, pdata->part, pdata->part_num);
meson_add_mtd_partitions_err:
pr_err("%s: add partition failed\n", __func__);
kfree(pdata);
return mtd_device_register(mtd, NULL, 0);
}
EXPORT_SYMBOL_GPL(meson_add_mtd_partitions);
MODULE_DESCRIPTION("MESON SPI NAND INTERFACE");
MODULE_AUTHOR("sunny luo<sunny.luo@amlogic.com>");
MODULE_LICENSE("GPL v2");