Files
android/drivers/misc/mediatek/nand/nandx/driver/nandx_ops.c
T
threeeye bc1d038eac REVVL 6 Pro kernel bring-up: full working tree snapshot (2026-09-12)
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)
2026-09-12 13:36:46 +00:00

715 lines
16 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 "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"
/*
* this file is just for pl & lk & mtd,
* mntl wrapper is independent
*/
struct ops_data {
long long base;
size_t len;
u8 *data;
u8 *oob;
};
struct wrap_ops {
long long offs;
size_t len;
u8 *data;
u8 *oob;
};
static struct nandx_lock nlock;
static int set_ops_table(struct nandx_chip_info *info,
struct nandx_ops *ops_table, int count,
struct ops_data *odata)
{
u32 start_row;
u32 page_size;
size_t tmp_len, tmp_count;
long long page_align_start;
struct nandx_ops *ops, *tmp_ops;
tmp_len = odata->len;
ops = ops_table;
tmp_ops = ops;
page_size = info->page_size;
page_align_start = div_round_down(odata->base, page_size);
start_row = (u32)div_down(page_align_start, page_size);
tmp_count = count;
while (tmp_len) {
ops->row = start_row;
if (ops == ops_table) {
ops->col = odata->base - page_align_start;
ops->len = MIN(page_size - ops->col, tmp_len);
ops->data = odata->data;
ops->oob = odata->oob;
} else {
ops->col = 0;
ops->len = MIN(page_size, tmp_len);
ops->data = tmp_ops->data + tmp_ops->len;
/* only one oob data */
ops->oob = odata->oob;
}
tmp_ops = ops;
tmp_len -= ops->len;
start_row++;
ops++;
tmp_count--;
}
NANDX_ASSERT(tmp_count == 0);
return 0;
}
static struct nandx_ops *get_error_ops(struct nandx_ops *ops_table, int count)
{
int i;
for (i = 0; i < count; i++) {
if (ops_table[i].status < 0 &&
ops_table[i].status != -ENANDFLIPS)
return &ops_table[i];
}
return NULL;
}
static int nandx_ops_block_rw(struct nandx_core *ncore,
struct wrap_ops *wrapops, bool is_raw,
bool do_multi, void *callback,
struct nandx_ops *err_ops)
{
size_t new_len;
int ret, ops_count, plane_num, mode = 0;
long long page_align_start, end;
u32 page_size;
struct ops_data odata;
struct nandx_ops *ops, *ops_table;
struct nandx_chip_info *info;
nandx_core_rw_cb nandx_core_rw;
info = ncore->info;
plane_num = info->plane_num;
if (plane_num > 1 && do_multi)
mode |= MODE_MULTI;
if (!do_multi)
plane_num = 1;
if (is_raw)
mode |= MODE_SLC;
page_size = info->page_size;
end = wrapops->offs + wrapops->len - 1;
page_align_start = div_round_down(wrapops->offs, page_size);
new_len = end - page_align_start + 1;
ops_count = div_up(new_len, page_size);
if (ops_count % plane_num) {
pr_err("%s: count %d not plane align\n", __func__, ops_count);
return -EINVAL;
}
ops_table = alloc_ops_table(ops_count);
if (!ops_table) {
NANDX_ASSERT(ops_table);
return -ENOMEM;
}
odata.base = wrapops->offs;
odata.len = wrapops->len;
odata.data = wrapops->data;
odata.oob = wrapops->oob;
ret = set_ops_table(info, ops_table, ops_count, &odata);
if (ret < 0)
goto err;
if (ops_count / plane_num > 1)
mode |= MODE_CACHE;
nandx_core_rw = (nandx_core_rw_cb)callback;
ret = nandx_core_rw(ops_table, ops_count, mode);
if (ret < 0 && ret != -ENANDFLIPS) {
if (err_ops) {
ops = get_error_ops(ops_table, ops_count);
NANDX_ASSERT(ops);
if (ops)
memcpy(err_ops, ops,
sizeof(struct nandx_ops));
}
goto err;
}
err:
free_ops_table(ops_table);
return ret;
}
static int nandx_write_error_handler(struct nandx_core *ncore,
u32 err_blk, u32 err_page,
u8 *buf, bool do_multi)
{
u8 *data;
bool raw_part;
size_t size;
long long err_addr, new_addr;
u32 new_block, oob_size, page_size, block_size;
int ret, plane_num;
struct wrap_ops wrap;
struct pt_resident *pt;
page_size = ncore->info->page_size;
block_size = ncore->info->block_size;
oob_size = ncore->info->oob_size;
err_addr = err_blk * block_size;
pt = nandx_pmt_get_partition(err_addr);
raw_part = nandx_pmt_is_raw_partition(pt);
plane_num = ncore->info->plane_num;
if (!do_multi)
plane_num = 1;
if (raw_part) {
data = mem_alloc(err_page, page_size);
if (!data) {
NANDX_ASSERT(buf);
return -ENOMEM;
}
size = err_page * page_size;
} else {
data = buf;
size = block_size * plane_num;
}
wrap.offs = err_addr;
wrap.len = size;
wrap.data = data;
wrap.oob = mem_alloc(1, oob_size);
if (!wrap.oob) {
ret = -ENOMEM;
goto read_err;
}
nandx_bmt_update_oob(err_blk, wrap.oob);
ret = nandx_ops_block_rw(ncore, &wrap, raw_part,
do_multi, nandx_core_read, NULL);
if (ret < 0)
goto oob_err;
new_block = nandx_ops_mark_bad(err_blk, UPDATE_WRITE_FAIL);
new_addr = new_block * block_size;
wrap.offs = new_addr;
ret = nandx_ops_block_rw(ncore, &wrap, raw_part,
do_multi, nandx_core_write, NULL);
oob_err:
mem_free(wrap.oob);
read_err:
if (raw_part)
mem_free(data);
return ret;
}
static int nandx_ops_rw(struct nandx_core *ncore,
struct wrap_ops *wrapops,
bool do_multi, void *callback)
{
u8 *data;
bool raw_part;
size_t size, left;
int ret, block_page_num;
long long addr, map_addr;
u32 page_size, oob_size, erase_size;
u32 err_page, block, map_block;
struct nandx_ops err_ops;
struct wrap_ops wrap;
struct pt_resident *pt;
struct nandx_chip_info *info;
info = ncore->info;
page_size = info->page_size;
oob_size = info->oob_size;
erase_size = info->block_size;
block_page_num = info->block_size / page_size;
addr = wrapops->offs;
left = wrapops->len;
data = wrapops->data;
pt = nandx_pmt_get_partition(addr);
raw_part = nandx_pmt_is_raw_partition(pt);
if (raw_part)
erase_size /= info->wl_page_num;
size = erase_size - reminder(wrapops->offs, erase_size);
size = MIN(wrapops->len, size);
wrap.oob = mem_alloc(1, oob_size);
if (!wrap.oob)
return -ENOMEM;
while (left) {
/*
* unmark it if want to support cross partitions r/w.
* pt = nandx_pmt_get_partition(addr);
* raw_part = nandx_pmt_is_raw_partition(pt);
* erase_size = info->block_size;
* if (raw_part)
* erase_size = info->block_size / info->wl_page_num;
*/
nandx_ops_addr_transfer(ncore, addr, &block, &map_block);
map_addr = (long long)map_block * info->block_size +
reminder(addr, erase_size);
wrap.offs = map_addr;
wrap.len = size;
wrap.data = data;
nandx_bmt_update_oob(block, wrap.oob);
ret = nandx_ops_block_rw(ncore, &wrap, raw_part,
do_multi, callback, &err_ops);
if (ret < 0 && ret != -ENANDFLIPS) {
if (err_ops.status != -ENANDWRITE)
goto oob_err;
err_page = err_ops.row % block_page_num;
ret = nandx_write_error_handler(ncore, map_block,
err_page, data,
do_multi);
if (ret < 0)
goto oob_err;
}
addr += size;
data += size;
left -= size;
size = MIN(left, erase_size);
}
oob_err:
mem_free(wrap.oob);
return 0;
}
static int nandx_ops_rw_oob(struct nandx_core *ncore, long long offs, u8 *oob,
void *callback)
{
bool raw_part;
int ret, mode = 0;
u32 page_size;
struct nandx_ops ops;
struct nandx_chip_info *info;
nandx_core_rw_cb nandx_core_rw;
struct pt_resident *pt;
info = ncore->info;
if (reminder(offs, info->page_size)) {
pr_err("%s: addr %llu not sector align\n", __func__, offs);
return -EINVAL;
}
pt = nandx_pmt_get_partition(offs);
raw_part = nandx_pmt_is_raw_partition(pt);
if (raw_part)
mode |= MODE_SLC;
page_size = info->page_size;
ops.row = (u32)div_down(offs, page_size);
ops.col = 0;
ops.len = 0;
ops.data = NULL;
ops.oob = oob;
nandx_core_rw = (nandx_core_rw_cb)callback;
ret = nandx_core_rw(&ops, 1, mode);
if (ret < 0)
goto err;
return 0;
err:
return ret;
}
int nandx_ops_read(struct nandx_core *ncore, long long from,
size_t len, u8 *buf, bool do_multi)
{
struct wrap_ops wrap;
int ret;
nandx_get_device(FL_READING);
wrap.offs = from;
wrap.len = len;
wrap.data = buf;
ret = nandx_ops_rw(ncore, &wrap, do_multi, nandx_core_read);
nandx_release_device();
return ret;
}
int nandx_ops_write(struct nandx_core *ncore, long long to,
size_t len, u8 *buf, bool do_multi)
{
struct wrap_ops wrap;
int ret;
nandx_get_device(FL_WRITING);
wrap.offs = to;
wrap.len = len;
wrap.data = buf;
ret = nandx_ops_rw(ncore, &wrap, do_multi, nandx_core_write);
nandx_release_device();
return ret;
}
int nandx_ops_read_oob(struct nandx_core *ncore, long long from, u8 *oob)
{
int ret;
nandx_get_device(FL_READING);
ret = nandx_ops_rw_oob(ncore, from, oob, nandx_core_read);
nandx_release_device();
return ret;
}
int nandx_ops_write_oob(struct nandx_core *ncore, long long to, u8 *oob)
{
int ret;
nandx_get_device(FL_WRITING);
ret = nandx_ops_rw_oob(ncore, to, oob, nandx_core_write);
nandx_release_device();
return ret;
}
int nandx_ops_erase_block(struct nandx_core *ncore, long long laddr)
{
bool raw_part;
int plane_num, ret = 0;
u32 block, map_block, mode = 0;
u32 rows[2], page_size, block_size, block_page_num;
struct nandx_chip_info *info;
struct pt_resident *pt;
nandx_get_device(FL_ERASING);
info = ncore->info;
page_size = info->page_size;
block_size = info->block_size;
block_page_num = block_size / page_size;
plane_num = info->plane_num;
pt = nandx_pmt_get_partition(laddr);
raw_part = nandx_pmt_is_raw_partition(pt);
if (raw_part) {
mode |= MODE_SLC;
plane_num = 1;
} else {
mode |= MODE_MULTI;
/* Todo: check how to erase non-raw partition */
NANDX_ASSERT(0);
}
rows[0] = nandx_ops_addr_transfer(ncore, laddr, &block, &map_block);
/*
* it is not correct for multi plane,
* but we need not to use multi operation now.
*/
if (plane_num > 1)
rows[1] = rows[0] + block_page_num;
ret = nandx_core_erase(rows, plane_num, mode);
if (ret < 0) {
pr_err("erase block 0x%x failed\n", map_block);
ret = nandx_ops_mark_bad(map_block, UPDATE_ERASE_FAIL);
}
nandx_release_device();
return ret;
}
int nandx_ops_erase(struct nandx_core *ncore, long long offs,
long long limit, size_t size)
{
long long cur_offs;
u32 erase_size, block_cnt;
struct nandx_chip_info *info;
struct pt_resident *pt;
bool raw_part;
info = ncore->info;
erase_size = info->block_size;
pt = nandx_pmt_get_partition(offs);
raw_part = nandx_pmt_is_raw_partition(pt);
if (raw_part)
erase_size /= info->wl_page_num;
if (reminder(offs, erase_size) || reminder(size, erase_size)) {
pr_debug("%s: invalid offs 0x%llx 0x%zx\n",
__func__, offs, size);
return -EINVAL;
}
/* calculate block number of this image */
block_cnt = size / erase_size;
/* erase nand block */
cur_offs = offs;
while (block_cnt) {
if (nandx_ops_erase_block(ncore, cur_offs) < 0) {
pr_warn("erase 0x%llx fail\n", cur_offs);
nandx_ops_mark_bad((u32)
div_down(cur_offs, erase_size),
UPDATE_ERASE_FAIL);
}
cur_offs += erase_size;
block_cnt--;
if (limit && block_cnt && cur_offs >= limit) {
pr_warn("off 0x%llx exceed 0x%llx\n",
cur_offs, limit);
return 0;
}
}
return 0;
}
int nandx_ops_mark_bad(u32 block, int reason)
{
return nandx_bmt_update(block, reason);
}
int nandx_ops_isbad(long long offs)
{
return 0;
}
u32 nandx_ops_addr_transfer(struct nandx_core *ncore, long long laddr,
u32 *blk, u32 *map_blk)
{
int block_page_num;
u32 page_size, block_size;
u32 page_in_block, block;
page_size = ncore->info->page_size;
block_size = ncore->info->block_size;
block_page_num = block_size / page_size;
nandx_pmt_addr_to_row(laddr, &block, &page_in_block);
*blk = block;
*map_blk = nandx_bmt_get_mapped_block(block);
return *map_blk * block_page_num + page_in_block;
}
void dump_nand_info(struct nandx_chip_info *info)
{
pr_info("block_num is %u\n", info->block_num);
pr_info("block_size is %u\n", info->block_size);
pr_info("page_size is %u\n", info->page_size);
pr_info("slc_block_size is %u\n", info->slc_block_size);
pr_info("oob_size is %u\n", info->oob_size);
pr_info("sector_size is %u\n", info->sector_size);
pr_info("plane_num is %d\n", info->plane_num);
pr_info("wl_page_num is %d\n", info->wl_page_num);
pr_info("ecc_strength is %u\n", info->ecc_strength);
}
u32 nandx_calculate_bmt_num(struct nandx_chip_info *info)
{
u32 ratio;
if (is_support_mntl())
ratio = info->block_num > 2000 ? 1 : 2;
else
ratio = 6;
return (info->block_num / 100 * ratio);
}
u32 nandx_get_chip_block_num(struct nandx_chip_info *info)
{
u32 chip_block_num, bmt_block_num;
bmt_block_num = nandx_calculate_bmt_num(info);
chip_block_num = info->block_num - bmt_block_num - PMT_POOL_SIZE;
return chip_block_num;
}
struct nfc_resource nandx_resource[] = {
/* NANDX_MT8127 */
{NANDX_MT8127, (void *)NFI_BASE, (void *)NFIECC_BASE,
(void *)0x10005000, 0, 0, NULL},
/* NANDX_MT8163 */
{NANDX_MT8163, (void *)NFI_BASE, (void *)NFIECC_BASE,
(void *)0x10005000, 0, 0, NULL},
/* NANDX_MT8167 */
{NANDX_MT8167, (void *)NFI_BASE, (void *)NFIECC_BASE,
(void *)0x10005000, 0, 0, NULL},
/* NANDX_NONE */
{NANDX_NONE, 0, 0, 0, 0, 0, NULL},
};
struct nandx_core *nandx_device_init(u32 mode)
{
int ret;
u32 bmt_block_num, pmt_block_start;
struct platform_data *pdata;
struct nandx_core *ncore;
struct nandx_chip_info *info;
enum IC_VER ver;
pdata = mem_alloc(1, sizeof(struct platform_data));
if (!pdata)
return NULL;
ver = nandx_get_chip_version();
pdata->res = nandx_get_nfc_resource(ver);
pdata->freq.freq_async = 133000000;
pdata->freq.sel_2x_idx = -1;
pdata->freq.sel_ecc_idx = -1;
ret = nandx_platform_init(pdata);
if (ret < 0)
goto pdata_err;
if (randomizer_is_support(pdata->res->ver))
mode |= MODE_RANDOMIZE;
ncore = nandx_core_init(pdata, mode);
if (!ncore)
goto platform_exit;
info = ncore->info;
dump_nand_info(info);
bmt_block_num = nandx_calculate_bmt_num(info);
ret = nandx_bmt_init(ncore->info, bmt_block_num, true);
if (ret < 0) {
pr_err("Error: init bmt failed\n");
NANDX_ASSERT(0);
goto bmt_err;
}
pmt_block_start = ncore->info->block_num - bmt_block_num;
pmt_block_start -= PMT_POOL_SIZE;
ret = nandx_pmt_init(ncore->info, pmt_block_start);
if (ret < 0) {
pr_err("Error: init pmt failed\n");
NANDX_ASSERT(0);
goto pmt_err;
}
return ncore;
pmt_err:
/* bmt_exit(); */
bmt_err:
nandx_core_free();
platform_exit:
nandx_platform_power_down(pdata);
pdata_err:
mem_free(pdata);
return NULL;
}
void nandx_lock_init(void)
{
init_waitqueue_head(&nlock.wq);
nlock.lock = nand_lock_create();
nlock.state = FL_READY;
}
struct nandx_lock *get_nandx_lock(void)
{
return &nlock;
}
int nandx_get_device(int new_state)
{
struct nandx_lock *nlock = get_nandx_lock();
struct nandx_core *ncore = get_nandx_core();
void *lock;
wait_queue_head_t *wq;
DECLARE_WAITQUEUE(wait, current);
bool high_speed_en, ecc_clk_en;
lock = nlock->lock;
wq = &nlock->wq;
retry:
nand_lock(lock);
if (nlock->state == FL_READY) {
if (new_state != FL_READY && new_state != FL_PM_SUSPENDED) {
high_speed_en = ncore->pdata->freq.sel_2x_idx >= 0;
ecc_clk_en = ncore->pdata->freq.sel_ecc_idx >= 0;
nandx_platform_enable_clock(ncore->pdata,
high_speed_en,
ecc_clk_en);
}
nlock->state = new_state;
nand_unlock(lock);
return 0;
}
set_current_state(TASK_UNINTERRUPTIBLE);
add_wait_queue(wq, &wait);
nand_unlock(lock);
schedule();
remove_wait_queue(wq, &wait);
goto retry;
}
void nandx_release_device(void)
{
struct nandx_lock *nlock = get_nandx_lock();
struct nandx_core *ncore = get_nandx_core();
void *lock;
wait_queue_head_t *wq;
bool high_speed_en, ecc_clk_en;
lock = nlock->lock;
wq = &nlock->wq;
/* Release the controller and the chip */
nand_lock(lock);
high_speed_en = ncore->pdata->freq.sel_2x_idx >= 0;
ecc_clk_en = ncore->pdata->freq.sel_ecc_idx >= 0;
if (nlock->state != FL_READY && nlock->state != FL_PM_SUSPENDED)
nandx_platform_disable_clock(ncore->pdata, high_speed_en,
ecc_clk_en);
nlock->state = FL_READY;
wake_up(wq);
nand_unlock(lock);
}