Squashed backup commit (no shallow-clone history issues) of xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream commit 9eece662e) plus all fixes applied to get everpal_defconfig building clean against a modern (GCC 15.2.0) toolchain -- see the previous commit message on the lineage-23.2 branch (also in this repo history is not available here since this is a squashed snapshot; full incremental history lives in the local DL380 checkout and the upstream GitHub remote). Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
456 lines
11 KiB
C
456 lines
11 KiB
C
/*
|
|
* Copyright (C) 2017 MediaTek Inc.
|
|
* Licensed under either
|
|
* BSD Licence, (see NOTICE for more details)
|
|
* GNU General Public License, version 2.0, (see NOTICE for more details)
|
|
*/
|
|
#include <linux/slab.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/types.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/partitions.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/time.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/io.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <linux/uaccess.h>
|
|
#include <asm/div64.h>
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/gpio.h>
|
|
#include <mt-plat/mtk_devinfo.h>
|
|
|
|
#include "nandx_util.h"
|
|
#include "nandx_errno.h"
|
|
#include "nandx_info.h"
|
|
#include "nandx_bmt.h"
|
|
#include "nandx_pmt.h"
|
|
#include "nandx_core.h"
|
|
#include "nandx_platform.h"
|
|
#include "nandx_ops.h"
|
|
#include "mtd_ops.h"
|
|
#include "wrapper_pmt.h"
|
|
#include "mntl_ops.h"
|
|
|
|
struct nfc_compatible {
|
|
enum IC_VER ic_ver;
|
|
char *ecc_compatible;
|
|
char *top_compatible;
|
|
};
|
|
|
|
static const struct nfc_compatible nfc_compats_mt8127 = {
|
|
.ic_ver = NANDX_MT8127,
|
|
.ecc_compatible = "mediatek,mt8127-nfiecc",
|
|
.top_compatible = NULL,
|
|
};
|
|
|
|
static const struct nfc_compatible nfc_compats_mt8163 = {
|
|
.ic_ver = NANDX_MT8163,
|
|
.ecc_compatible = "mediatek,mt8163-nfiecc",
|
|
.top_compatible = NULL,
|
|
};
|
|
|
|
static const struct nfc_compatible nfc_compats_mt8167 = {
|
|
.ic_ver = NANDX_MT8167,
|
|
.ecc_compatible = "mediatek,mt8167-nfiecc",
|
|
.top_compatible = "mediatek,mt8167-pctl-a-syscfg",
|
|
};
|
|
|
|
static const struct of_device_id ic_of_match[] = {
|
|
{.compatible = "mediatek,mt8127-nfi", .data = &nfc_compats_mt8127},
|
|
{.compatible = "mediatek,mt8163-nfi", .data = &nfc_compats_mt8163},
|
|
{.compatible = "mediatek,mt8167-nfi", .data = &nfc_compats_mt8167},
|
|
{}
|
|
};
|
|
|
|
static void release_platform_data(struct platform_data *pdata)
|
|
{
|
|
mem_free(pdata->res);
|
|
mem_free(pdata);
|
|
}
|
|
|
|
static struct platform_data *config_platform_data(struct platform_device
|
|
*pdev)
|
|
{
|
|
u32 nfi_irq, ecc_irq;
|
|
void __iomem *nfi_base, *nfiecc_base, *top_base = NULL;
|
|
struct device *dev;
|
|
struct device_node *nfiecc_node, *top_node;
|
|
const struct of_device_id *of_id;
|
|
struct nfc_compatible *compat;
|
|
struct platform_data *pdata;
|
|
struct nfc_resource *res;
|
|
|
|
pdata = mem_alloc(1, sizeof(struct platform_data));
|
|
if (!pdata)
|
|
return NULL;
|
|
|
|
res = mem_alloc(1, sizeof(struct nfc_resource));
|
|
if (!res)
|
|
goto freepdata;
|
|
|
|
pdata->res = res;
|
|
|
|
dev = &pdev->dev;
|
|
nfi_base = of_iomap(dev->of_node, 0);
|
|
nfi_irq = irq_of_parse_and_map(dev->of_node, 0);
|
|
|
|
of_id = of_match_node(ic_of_match, pdev->dev.of_node);
|
|
if (!of_id)
|
|
goto freeres;
|
|
|
|
compat = (struct nfc_compatible *)of_id->data;
|
|
nfiecc_node = of_find_compatible_node(NULL, NULL,
|
|
compat->ecc_compatible);
|
|
nfiecc_base = of_iomap(nfiecc_node, 0);
|
|
ecc_irq = irq_of_parse_and_map(nfiecc_node, 0);
|
|
|
|
if (compat->top_compatible) {
|
|
top_node = of_find_compatible_node(NULL, NULL,
|
|
compat->top_compatible);
|
|
top_base = of_iomap(top_node, 0);
|
|
}
|
|
|
|
res->ver = (enum IC_VER)(compat->ic_ver);
|
|
res->nfi_regs = (void *)nfi_base;
|
|
res->ecc_regs = (void *)nfiecc_base;
|
|
if (top_base)
|
|
res->top_regs = (void *)top_base;
|
|
res->nfi_irq = nfi_irq;
|
|
res->ecc_irq = ecc_irq;
|
|
res->dev = pdev;
|
|
|
|
pdata->freq.freq_async = 133000000;
|
|
pdata->freq.sel_2x_idx = -1;
|
|
pdata->freq.sel_ecc_idx = -1;
|
|
|
|
return pdata;
|
|
|
|
freeres:
|
|
mem_free(res);
|
|
freepdata:
|
|
mem_free(pdata);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct mtd_info *mtd_info_create(struct platform_device *pdev,
|
|
struct nandx_core *ncore)
|
|
{
|
|
struct mtd_info *mtd;
|
|
|
|
mtd = mem_alloc(1, sizeof(struct mtd_info));
|
|
if (!mtd)
|
|
return NULL;
|
|
|
|
/* Fill in remaining MTD driver data */
|
|
mtd->priv = ncore;
|
|
mtd->owner = THIS_MODULE;
|
|
mtd->dev.parent = &pdev->dev;
|
|
mtd->name = "MTK-Nand";
|
|
mtd->writesize = ncore->info->page_size;
|
|
mtd->erasesize = ncore->info->block_size;
|
|
mtd->oobsize = ncore->info->oob_size;
|
|
mtd->size = (u64)ncore->info->block_size * ncore->info->block_num;
|
|
mtd->type = MTD_NANDFLASH;
|
|
mtd->flags = MTD_CAP_NANDFLASH;
|
|
mtd->_erase = nand_erase;
|
|
mtd->_point = NULL;
|
|
mtd->_unpoint = NULL;
|
|
mtd->_read = nand_read;
|
|
mtd->_write = nand_write;
|
|
mtd->_panic_write = nand_write;
|
|
mtd->_read_oob = nand_read_oob;
|
|
mtd->_write_oob = nand_write_oob;
|
|
mtd->_sync = nand_sync;
|
|
mtd->_lock = NULL;
|
|
mtd->_unlock = NULL;
|
|
mtd->_block_isbad = nand_is_bad;
|
|
mtd->_block_markbad = nand_mark_bad;
|
|
mtd->writebufsize = mtd->writesize;
|
|
|
|
/* propagate ecc info to mtd_info */
|
|
/* TODO: set NULL TO */
|
|
mtd->ecc_strength = ncore->info->ecc_strength;
|
|
mtd->ecc_step_size = ncore->info->sector_size;
|
|
/*
|
|
* Initialize bitflip_threshold to its default prior scan_bbt() call.
|
|
* scan_bbt() might invoke mtd_read(), thus bitflip_threshold must be
|
|
* properly set.
|
|
*/
|
|
if (!mtd->bitflip_threshold)
|
|
mtd->bitflip_threshold = mtd->ecc_strength;
|
|
|
|
return mtd;
|
|
}
|
|
|
|
static void mtd_info_release(struct mtd_info *mtd)
|
|
{
|
|
mem_free(mtd);
|
|
}
|
|
|
|
bool randomizer_is_support(enum IC_VER ver)
|
|
{
|
|
u32 idx = 0, enable = 0;
|
|
|
|
if (ver == NANDX_MT8167) {
|
|
idx = 0;
|
|
enable = 0x00001000;
|
|
} else if (ver == NANDX_MT8127 || ver == NANDX_MT8163) {
|
|
idx = 26;
|
|
enable = 0x00000004;
|
|
}
|
|
|
|
return (get_devinfo_with_index(idx) & enable) ? true : false;
|
|
}
|
|
|
|
static int nand_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
u32 mode, bmt_block_num, pmt_block_start;
|
|
struct mtd_info *mtd;
|
|
struct platform_data *pdata;
|
|
struct nandx_core *ncore;
|
|
struct nandx_chip_info *info;
|
|
|
|
/* get info from dts & config pdata */
|
|
pdata = config_platform_data(pdev);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
|
|
ret = nandx_platform_init(pdata);
|
|
if (ret < 0)
|
|
goto release_pdata;
|
|
|
|
nandx_lock_init();
|
|
|
|
mode = MODE_DMA | MODE_ECC | MODE_IRQ | MODE_DDR | MODE_CALIBRATION;
|
|
if (randomizer_is_support(pdata->res->ver))
|
|
mode |= MODE_RANDOMIZE;
|
|
|
|
ncore = nandx_core_init(pdata, mode);
|
|
if (ret < 0)
|
|
goto initfail;
|
|
|
|
info = ncore->info;
|
|
dump_nand_info(info);
|
|
bmt_block_num = nandx_calculate_bmt_num(info);
|
|
ret = nandx_bmt_init(info, bmt_block_num, true);
|
|
if (ret < 0) {
|
|
pr_err("Error: init bmt failed\n");
|
|
NANDX_ASSERT(0);
|
|
goto bmt_err;
|
|
}
|
|
|
|
pmt_block_start = info->block_num - bmt_block_num - PMT_POOL_SIZE;
|
|
ret = nandx_pmt_init(info, pmt_block_start);
|
|
if (ret < 0) {
|
|
pr_err("Error: init pmt failed\n");
|
|
NANDX_ASSERT(0);
|
|
goto pmt_err;
|
|
}
|
|
|
|
mtd = mtd_info_create(pdev, ncore);
|
|
if (!mtd) {
|
|
ret = -ENOMEM;
|
|
goto mtd_err;
|
|
}
|
|
|
|
nandx_pmt_register(mtd);
|
|
|
|
platform_set_drvdata(pdev, mtd);
|
|
|
|
/* mntl related init */
|
|
ret = nandx_mntl_data_info_alloc();
|
|
if (ret < 0)
|
|
goto data_init_err;
|
|
|
|
ret = nandx_mntl_ops_init();
|
|
if (ret < 0)
|
|
goto ops_init_err;
|
|
|
|
ret = init_mntl_module();
|
|
if (ret < 0)
|
|
goto ops_init_err;
|
|
|
|
return 0;
|
|
|
|
ops_init_err:
|
|
nandx_mntl_data_info_free();
|
|
data_init_err:
|
|
nandx_pmt_unregister();
|
|
mtd_info_release(mtd);
|
|
mtd_err:
|
|
nandx_pmt_exit();
|
|
pmt_err:
|
|
nandx_bmt_exit();
|
|
bmt_err:
|
|
nandx_core_free();
|
|
initfail:
|
|
nandx_platform_power_down(pdata);
|
|
release_pdata:
|
|
release_platform_data(pdata);
|
|
|
|
pr_debug("%s: probe err %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int nand_remove(struct platform_device *pdev)
|
|
{
|
|
struct mtd_info *mtd;
|
|
struct platform_data *pdata;
|
|
struct nandx_core *ncore;
|
|
bool high_speed_en, ecc_clk_en;
|
|
|
|
mtd = platform_get_drvdata(pdev);
|
|
ncore = (struct nandx_core *)mtd->priv;
|
|
pdata = ncore->pdata;
|
|
|
|
mtd_device_unregister(mtd);
|
|
high_speed_en = pdata->freq.sel_2x_idx >= 0;
|
|
ecc_clk_en = pdata->freq.sel_ecc_idx >= 0;
|
|
nandx_platform_unprepare_clock(ncore->pdata, high_speed_en,
|
|
ecc_clk_en);
|
|
nandx_platform_power_down(pdata);
|
|
release_platform_data(pdata);
|
|
mtd_info_release(mtd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nand_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
int ret = 0;
|
|
struct mtd_info *mtd;
|
|
struct platform_data *pdata;
|
|
struct nandx_core *ncore;
|
|
bool high_speed_en, ecc_clk_en;
|
|
|
|
pr_info("%s\n", __func__);
|
|
nandx_get_device(FL_PM_SUSPENDED);
|
|
|
|
mtd = platform_get_drvdata(pdev);
|
|
ncore = (struct nandx_core *)mtd->priv;
|
|
pdata = ncore->pdata;
|
|
|
|
high_speed_en = pdata->freq.sel_2x_idx >= 0;
|
|
ecc_clk_en = pdata->freq.sel_ecc_idx >= 0;
|
|
nandx_platform_enable_clock(ncore->pdata, high_speed_en, ecc_clk_en);
|
|
ret = nandx_core_suspend();
|
|
|
|
nandx_platform_disable_clock(ncore->pdata, high_speed_en, ecc_clk_en);
|
|
nandx_platform_unprepare_clock(ncore->pdata, high_speed_en,
|
|
ecc_clk_en);
|
|
|
|
nandx_platform_power_down(pdata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int nand_resume(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct mtd_info *mtd;
|
|
struct platform_data *pdata;
|
|
struct nandx_core *ncore;
|
|
struct nandx_lock *nlock = get_nandx_lock();
|
|
bool high_speed_en, ecc_clk_en;
|
|
|
|
pr_info("%s\n", __func__);
|
|
mtd = platform_get_drvdata(pdev);
|
|
ncore = (struct nandx_core *)mtd->priv;
|
|
pdata = ncore->pdata;
|
|
|
|
nandx_platform_power_on(pdata);
|
|
|
|
nandx_platform_prepare_clock(ncore->pdata, false, false);
|
|
nandx_platform_enable_clock(ncore->pdata, false, false);
|
|
ret = nandx_core_resume();
|
|
nandx_platform_disable_clock(ncore->pdata, false, false);
|
|
nandx_platform_unprepare_clock(ncore->pdata, false, false);
|
|
|
|
high_speed_en = pdata->freq.sel_2x_idx >= 0;
|
|
ecc_clk_en = pdata->freq.sel_ecc_idx >= 0;
|
|
nandx_platform_prepare_clock(ncore->pdata, high_speed_en, ecc_clk_en);
|
|
|
|
if (nlock->state == FL_PM_SUSPENDED)
|
|
nandx_release_device();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct platform_driver nand_driver = {
|
|
.probe = nand_probe,
|
|
.remove = nand_remove,
|
|
.suspend = nand_suspend,
|
|
.resume = nand_resume,
|
|
.driver = {
|
|
.name = "mtk-nand",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = ic_of_match,
|
|
},
|
|
};
|
|
|
|
#define PROCNAND "driver/nand"
|
|
|
|
static int nand_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
struct nandx_core *ncore = get_nandx_core();
|
|
struct nandx_chip_info *info = ncore->info;
|
|
u64 size = (u64)info->block_size * info->block_num;
|
|
|
|
seq_puts(m, "Size");
|
|
seq_printf(m, "0x%llx\n", size);
|
|
seq_printf(m, "0x%x\n", info->page_size);
|
|
seq_printf(m, "0x%x\n", info->oob_size);
|
|
seq_puts(m, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nand_proc_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, nand_proc_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations nand_fops = {
|
|
.open = nand_proc_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int __init nand_init(void)
|
|
{
|
|
proc_create(PROCNAND, 0664, NULL, &nand_fops);
|
|
|
|
return platform_driver_register(&nand_driver);
|
|
}
|
|
|
|
static void __exit nand_exit(void)
|
|
{
|
|
platform_driver_unregister(&nand_driver);
|
|
}
|
|
module_init(nand_init);
|
|
module_exit(nand_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MTK Nand Flash Controller Driver");
|
|
MODULE_AUTHOR("MediaTek");
|