Files
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

2007 lines
48 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/sched/clock.h>
#include <linux/sched/debug.h>
#include <linux/device.h>
#include "nandx_util.h"
#include "nandx_errno.h"
#include "nandx_bmt.h"
#include "nandx_info.h"
#include "nandx_core.h"
#include "nandx_ops.h"
#include "wrapper_pmt.h"
#include "mntl_ops.h"
#include "mntl_mem.h"
int g_i4Homescreen;
struct nandx_ops *ops_table;
struct mtk_nand_data_info *data_info;
static inline bool block_page_num_is_valid(struct mtk_nand_chip_info *info,
u32 block, u32 page);
static int open_block_delete(struct open_block *open, int block);
static bool should_close_block(void);
static bool is_ewrite_block(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, u32 block);
static void dump_block_bitmap(void);
/********* MTK Nand Driver Related Functnions *********/
/* mtk_isbad_block
* Mark specific block as bad block, update bad block list and bad block table
* @block: block address to markbad
*/
bool mtk_isbad_block(u32 block)
{
struct mtk_nand_chip_bbt_info *chip_bbt;
u32 i;
chip_bbt = &data_info->chip_bbt;
for (i = 0; i < chip_bbt->bad_block_num; i++) {
if (block == chip_bbt->bad_block_table[i]) {
pr_debug("Check block:0x%x is bad\n", block);
return TRUE;
}
}
return FALSE;
}
static bool is_slc_block(struct mtk_nand_chip_info *info, u32 block)
{
int index, bit;
if (info->types != MTK_NAND_FLASH_TLC)
return block >= info->data_block_num;
index = block / 32;
bit = block % 32;
if ((info->block_type_bitmap[index] >> bit) & 1)
return FALSE;
return TRUE;
}
static int mtk_nand_read_pages(struct mtk_nand_chip_info *info,
u8 *data_buffer, u8 *oob_buffer,
u32 block, u32 page, u32 offset, u32 size)
{
int fdm_size;
static u8 *fdm_buf;
#ifdef MTK_FORCE_READ_FULL_PAGE
static u8 *page_buf;
#endif
u32 page_addr, page_size, oob_size;
u32 sect_num, start_sect;
u32 col_addr, sect_read;
u32 mode = 0;
int ret = 0;
struct nandx_ops ops;
nandx_get_device(FL_READING);
page_addr =
(block + data_info->bmt.start_block) * info->data_page_num + page;
/* For log area access */
if (is_slc_block(info, block)) {
mode |= MODE_SLC;
page_size = info->log_page_size;
oob_size = info->log_oob_size;
} else {
page_size = info->data_page_size;
oob_size = info->log_oob_size;
}
if (fdm_buf == NULL) {
fdm_buf = kmalloc(1024, GFP_KERNEL);
if (fdm_buf == NULL) {
ret = -ENOMEM;
goto exit;
}
}
if (size < page_size) {
/* Sector read case */
sect_num = page_size / (1 << info->sector_size_shift);
start_sect = offset / (1 << info->sector_size_shift);
col_addr = start_sect * (1 << info->sector_size_shift);
sect_read = size / (1 << info->sector_size_shift);
fdm_size = oob_size / sect_num;
#ifdef MTK_FORCE_READ_FULL_PAGE
if (page_buf == NULL) {
page_buf = kmalloc(page_size, GFP_KERNEL);
if (page_buf == NULL) {
ret = -ENOMEM;
goto exit;
}
}
ops.row = page_addr;
ops.col = 0;
ops.len = page_size;
ops.data = page_buf;
ops.oob = fdm_buf;
ret = nandx_core_read(&ops, 1, mode);
memcpy(data_buffer, page_buf + offset, size);
memcpy(oob_buffer, fdm_buf + start_sect * fdm_size,
sect_read * fdm_size);
#else
ops.row = page_addr;
ops.col = col_addr;
ops.len = size;
ops.data = data_buffer;
ops.oob = fdm_buf;
ret = nandx_core_read(&ops, 1, mode);
memcpy(oob_buffer, fdm_buf, sect_read * fdm_size);
#endif
} else {
ops.row = page_addr;
ops.col = 0;
ops.len = page_size;
ops.data = data_buffer;
ops.oob = fdm_buf;
ret = nandx_core_read(&ops, 1, mode);
memcpy(oob_buffer, fdm_buf, oob_size);
}
if (ret < 0) {
ret = ops.status;
if (ret == -ENANDFLIPS)
pr_err("bitflip @ b:%d p:%d\n", block, page);
} else {
ret = 0;
}
exit:
if (ret)
pr_err("read err:%d, b:%d p:%d off:%d size:%d\n",
ret, block, page, offset, size);
nandx_release_device();
return ret;
}
static int mtk_nand_read_multi_pages(struct mtk_nand_chip_info *info,
int page_num,
struct mtk_nand_chip_read_param *param)
{
u32 mode = 0, ppb;
int i, ret;
struct nandx_ops *ops;
nandx_get_device(FL_READING);
ops = alloc_ops_table(page_num);
if (!ops) {
ret = -ENOMEM;
goto err;
}
ppb = info->data_page_num;
if (is_slc_block(info, param[0].block)) {
mode |= MODE_SLC;
ppb = info->log_page_num;
}
if (info->plane_num > 1)
mode |= MODE_MULTI;
for (i = 0; i < page_num; i++) {
ops[i].row = param[i].block * ppb + param[i].page;
ops[i].col = param[i].offset;
ops[i].len = param[i].size;
ops[i].data = param[i].data_buffer;
ops[i].oob = param[i].oob_buffer;
}
ret = nandx_core_read(ops, page_num, mode);
err:
nandx_release_device();
return ret;
}
static inline struct list_node *get_list_item_prev(struct list_node *head,
struct list_node *cur)
{
struct list_node *item;
item = head;
while ((item->next != cur) && (item->next != NULL))
item = item->next;
if (item->next != cur) {
pr_err("check no node cur:%p\n", cur);
return NULL;
}
return item;
}
static inline struct list_node *get_list_tail(struct list_node *head)
{
struct list_node *item;
item = head;
while (item->next != NULL)
item = item->next;
return item;
}
static inline struct nand_work *get_list_work(struct list_node *node)
{
return containerof(node, struct nand_work, list);
}
static struct list_node *seek_list_item(struct list_node *cur, u32 offset)
{
struct list_node *item;
int i;
i = 0;
item = cur;
while (i < offset) {
item = item->next;
i++;
}
return item;
}
static inline void lock_list(struct worklist_ctrl *list_ctrl)
{
spin_lock(&list_ctrl->list_lock);
}
static inline void unlock_list(struct worklist_ctrl *list_ctrl)
{
spin_unlock(&list_ctrl->list_lock);
}
static inline int add_list_node(struct worklist_ctrl *list_ctrl,
struct list_node *node)
{
struct list_node *tail;
struct mtk_nand_chip_operation *ops;
int ret = 0;
lock_list(list_ctrl);
ops = &get_list_work(node)->ops;
if (ops->types == MTK_NAND_OP_WRITE &&
is_ewrite_block(ops->info, list_ctrl, ops->block)) {
ops->callback(ops->info, ops->data_buffer, ops->oob_buffer,
ops->block, ops->page, -ENANDWRITE,
ops->userdata);
ret = -ENANDWRITE;
goto OUT;
}
tail = get_list_tail(&list_ctrl->head);
tail->next = node;
list_ctrl->total_num++;
OUT:
unlock_list(list_ctrl);
return ret;
}
static inline int get_list_work_cnt(struct worklist_ctrl *list_ctrl)
{
int cnt = 0;
lock_list(list_ctrl);
cnt = list_ctrl->total_num;
unlock_list(list_ctrl);
return cnt;
}
static bool is_multi_plane(struct mtk_nand_chip_info *minfo)
{
struct nandx_chip_info *info;
info = get_chip_info();
return (info->plane_num > 1) ? true : false;
}
static bool is_multi_read_support(struct mtk_nand_chip_info *info)
{
return is_multi_plane(info) /*&& (devinfo.vendor == VEND_MICRON) */;
}
static bool are_on_diff_planes(u32 block0, u32 block1)
{
return (block0 + block1) & 0x1;
}
static bool can_multi_plane(u32 block0, u32 page0, u32 block1, u32 page1)
{
return ((block0 + block1) & 0x1) && (page0 == page1);
}
static bool can_ops_multi_plane(struct mtk_nand_chip_operation *ops0,
struct mtk_nand_chip_operation *ops1)
{
struct mtk_nand_chip_info *info = ops0->info;
if (!is_multi_plane(info))
return false;
return can_multi_plane(ops0->block, ops0->page, ops1->block,
ops1->page);
}
static void call_multi_work_callback(struct list_node *cur, int count,
int status)
{
int i;
struct list_node *item;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
item = cur;
for (i = 0; i < count; i++) {
if (item == NULL) {
pr_err("%s: NULL item\n", __func__);
return;
}
work = get_list_work(item);
ops = &work->ops;
ops->callback(ops->info, ops->data_buffer,
ops->oob_buffer, ops->block,
ops->page, status, ops->userdata);
if (i >= count)
break;
item = item->next;
}
}
static void call_tlc_page_group_callback(struct list_node *base_node,
int start, int end, bool multi_plane,
int status)
{
struct list_node *item;
int i;
int count = multi_plane ? 2 : 1;
item = seek_list_item(base_node, start * count);
for (i = 0; i < end - start + 1; i++) {
call_multi_work_callback(item, count, status);
if (i >= end - start)
break;
if (count == 1)
item = item->next;
else
item = item->next->next;
}
}
static u32 callback_and_free_ewrite(struct worklist_ctrl *list_ctrl,
int ecount)
{
int *ewrite;
int i;
struct list_node *item;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
if (ecount != 1 && ecount != 2) {
pr_err("ecount:%d\n", ecount);
return 0;
}
ewrite = list_ctrl->ewrite;
item = list_ctrl->head.next;
i = 0;
lock_list(list_ctrl);
while (item != NULL) {
work = get_list_work(item);
ops = &work->ops;
if (i < ecount)
ewrite[i] = ops->block;
else if (ops->block != ewrite[i % ecount])
break;
ops->callback(ops->info, ops->data_buffer, ops->oob_buffer,
ops->block, ops->page, -ENANDWRITE,
ops->userdata);
list_ctrl->total_num--;
item = item->next;
kfree(work);
i++;
}
list_ctrl->head.next = item;
unlock_list(list_ctrl);
return i;
}
static bool is_ewrite_block(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, u32 block)
{
int i;
for (i = 0; i < info->plane_num; i++)
if (block == list_ctrl->ewrite[i])
return true;
return false;
}
static struct list_node *free_multi_work(struct worklist_ctrl *list_ctrl,
struct list_node *head,
struct list_node *start_node,
int count)
{
int i;
struct list_node *item, *prev, *next;
struct nand_work *work;
item = start_node;
lock_list(list_ctrl);
prev = get_list_item_prev(head, start_node);
if (prev == NULL) {
pr_err("prev is NULL!!\n");
return NULL;
}
for (i = 0; i < count; i++) {
if (item == NULL) {
pr_err("%s: NULL item\n", __func__);
return NULL;
}
work = get_list_work(item);
next = item->next;
list_ctrl->total_num--;
kfree(work);
item = next;
}
prev->next = item;
unlock_list(list_ctrl);
return item;
}
static struct list_node *free_multi_write_work(struct worklist_ctrl
*list_ctrl,
struct list_node *start_node,
int count)
{
struct list_node *head, *next;
head = &list_ctrl->head;
next = free_multi_work(list_ctrl, head, start_node, count);
return next;
}
static struct list_node *free_multi_erase_work(struct worklist_ctrl
*list_ctrl,
struct list_node *start_node,
int count)
{
struct list_node *head, *next;
head = &list_ctrl->head;
next = free_multi_work(list_ctrl, head, start_node, count);
return next;
}
static struct list_node *free_tlc_page_group_work(struct worklist_ctrl
*list_ctrl,
struct list_node *base_node,
int start, int end,
bool multi_plane)
{
struct list_node *start_node;
int i;
int count = multi_plane ? 2 : 1;
start_node = seek_list_item(base_node, start * count);
for (i = 0; i < end - start + 1; i++) {
start_node =
free_multi_write_work(list_ctrl, start_node, count);
}
return start_node;
}
static u32 do_mlc_multi_plane_write(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl,
struct list_node *start_node, int count)
{
u32 ret = 0, mode = 0;
int i, status = 0, odd = 0;
struct list_node *item;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
u32 page = 0, block0 = 0, block1 = 0, block_num;
bool multi_plane = false;
nandx_get_device(FL_WRITING);
if (count != info->max_keep_pages && count != info->plane_num) {
pr_err("%s: count:%d max:%d(or %d)\n",
__func__, count, info->max_keep_pages,
info->plane_num);
goto err;
}
if (start_node == NULL) {
pr_err("%s: start node is NULL!\n", __func__);
goto err;
}
item = start_node;
ret = count;
if (should_close_block()) {
work = get_list_work(item);
page = work->ops.page;
block0 = work->ops.block;
if (is_multi_plane(info))
block1 = get_list_work(item->next)->ops.block;
}
multi_plane = is_multi_plane(info);
for (i = 0; i < count; i++) {
work = get_list_work(item);
ops = &work->ops;
item = item->next;
block_num = ops->block + data_info->bmt.start_block;
if (i == 0)
mode |= is_slc_block(info, ops->block) ? MODE_SLC : 0;
if (multi_plane) {
if (!(i & 1))
odd = block_num & 1;
else if ((block_num & 1) == odd)
multi_plane = false;
}
ops_table[i].row =
block_num * info->data_page_num + ops->page;
ops_table[i].col = 0;
ops_table[i].len = info->data_page_size;
ops_table[i].data = ops->data_buffer;
ops_table[i].oob = ops->oob_buffer;
}
if (multi_plane)
mode |= MODE_MULTI;
status = nandx_core_write(ops_table, count, mode);
item = start_node;
if (status == -ENANDWRITE) {
ret = callback_and_free_ewrite(list_ctrl, count);
} else {
call_multi_work_callback(item, count, status);
free_multi_write_work(list_ctrl, item, count);
}
if (should_close_block() &&
(status || (page == (info->data_page_num - 1)))) {
open_block_delete(&data_info->open, block0);
if (is_multi_plane(info))
open_block_delete(&data_info->open, block1);
}
err:
nandx_release_device();
return ret;
}
static u32 do_tlc_write(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl,
struct list_node *start_node, int count)
{
u32 ret = 0, mode = 0;
int i, status = 0, odd = 0;
struct list_node *item;
u32 block0, block1 = 0, block_num;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
bool multi_plane = false, last_page = false;
nandx_get_device(FL_WRITING);
if (count != info->max_keep_pages && count != info->plane_num) {
pr_err("%s: count:%d max:%d(or %d)\n",
__func__, count, info->max_keep_pages,
info->plane_num);
goto err;
}
if (start_node == NULL) {
pr_err("%s: start node is NULL!\n", __func__);
goto err;
}
work = get_list_work(start_node);
ops = &work->ops;
mode |= is_slc_block(info, ops->block) ? MODE_SLC : 0;
ret = count;
item = start_node;
multi_plane = is_multi_plane(info);
for (i = 0; i < count; i++) {
work = get_list_work(item);
ops = &work->ops;
item = item->next;
block_num = ops->block + data_info->bmt.start_block;
if (multi_plane) {
if (!(i & 1))
odd = block_num & 1;
else if ((block_num & 1) == odd)
multi_plane = false;
}
ops_table[i].row =
block_num * info->data_page_num + ops->page;
ops_table[i].col = 0;
ops_table[i].len = info->data_page_size;
ops_table[i].data = ops->data_buffer;
ops_table[i].oob = ops->oob_buffer;
}
if (multi_plane)
mode |= MODE_MULTI;
status = nandx_core_write(ops_table, count, mode);
item = start_node;
if (status == -ENANDWRITE) {
ret =
callback_and_free_ewrite(list_ctrl, multi_plane ? 2 : 1);
} else if (mode & MODE_SLC) {
call_multi_work_callback(item, count, status);
free_multi_write_work(list_ctrl, item, count);
} else {
call_tlc_page_group_callback(item, 0, 2, multi_plane, status);
if (multi_plane)
work = get_list_work(seek_list_item(item, 8 * 2));
else
work = get_list_work(seek_list_item(item, 8));
item = free_tlc_page_group_work(list_ctrl, item, 0, 2,
multi_plane);
ops = &work->ops;
if (ops->page == (info->data_page_num - 1)) {
/* 3~5 */
call_tlc_page_group_callback(item, 0, 2,
multi_plane, status);
/* 6~8 */
call_tlc_page_group_callback(item, 3, 5,
multi_plane, status);
/* 3~5 */
item = free_tlc_page_group_work(list_ctrl, item,
0, 2, multi_plane);
/* 6~8 */
item = free_tlc_page_group_work(list_ctrl, item,
0, 2, multi_plane);
last_page = true;
}
}
if (should_close_block() && (status || last_page)) {
open_block_delete(&data_info->open, block0);
if (multi_plane)
open_block_delete(&data_info->open, block1);
}
err:
nandx_release_device();
return ret;
}
static u32 complete_erase_count(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, int total)
{
struct list_node *head;
struct nand_work *work0, *work1;
bool is2p;
if (!total)
return 0;
if (!is_multi_plane(info))
return 1;
/* multi-plane */
head = &list_ctrl->head;
if (total < info->plane_num) {
work0 = get_list_work(seek_list_item(head, total));
return (!work0->ops.more) ? total : 0;
}
/* total >= plane_num */
work0 = get_list_work(head->next);
if (!head->next->next)
pr_err("%s: second NULL, total:%d, work0->block:%d\n",
__func__, total, work0->ops.block);
work1 = get_list_work(head->next->next);
is2p = are_on_diff_planes(work0->ops.block, work1->ops.block);
return is2p ? info->plane_num : 1;
}
static u32 complete_slc_write_count(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl,
int total)
{
struct list_node *head;
struct nand_work *work0, *work1;
bool multi_op;
if (!total)
return 0;
if (!is_multi_plane(info))
return 1;
/* multi-plane */
if (total < info->plane_num)
return 0;
/* total >= plane_num */
head = &list_ctrl->head;
work0 = get_list_work(head->next);
work1 = get_list_work(head->next->next);
multi_op = can_ops_multi_plane(&work0->ops, &work1->ops);
return multi_op ? 2 : 1;
}
static u32 complete_write_count(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, int total)
{
struct list_node *head;
struct nand_work *work0, *work1;
bool multi_op;
if (!total)
return 0;
head = &list_ctrl->head;
work0 = get_list_work(head->next);
if (!is_slc_block(info, work0->ops.block))
goto non_slc;
if (!is_multi_plane(info))
return 1;
if (total < info->plane_num)
return 0;
work1 = get_list_work(head->next->next);
if (!is_slc_block(info, work1->ops.block)) {
pr_err("%s: block0 %d is slc, but block1 %d is not\n",
__func__, work0->ops.block, work1->ops.block);
return 0;
}
multi_op = can_ops_multi_plane(&work0->ops, &work1->ops);
return multi_op ? 2 : 1;
non_slc:
return (total >= info->max_keep_pages) ? info->max_keep_pages : 0;
}
static int init_list_ctrl(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl,
enum worklist_type type,
get_ready_count get_ready_count_func,
process_list_data process_data_func)
{
int i;
mutex_init(&list_ctrl->sync_lock);
spin_lock_init(&list_ctrl->list_lock);
list_ctrl->type = type;
list_ctrl->total_num = 0;
list_ctrl->head.next = NULL;
list_ctrl->get_ready_count_func = get_ready_count_func;
list_ctrl->process_data_func = process_data_func;
list_ctrl->ewrite = kmalloc_array(info->plane_num, sizeof(int),
GFP_KERNEL);
for (i = 0; i < info->plane_num; i++)
list_ctrl->ewrite[i] = -1;
return 0;
}
static inline bool block_num_is_valid(struct mtk_nand_chip_info *info,
u32 block)
{
return (block >= 0 && block <
(info->data_block_num + info->log_block_num));
}
static inline bool block_page_num_is_valid(struct mtk_nand_chip_info *info,
u32 block, u32 page)
{
if (!block_num_is_valid(info, block))
return false;
if (page < 0)
return false;
if ((!is_slc_block(info, block) &&
page < info->data_page_num) ||
(is_slc_block(info, block) && page < info->log_page_num))
return true;
else
return false;
}
static int open_block_init(struct open_block *open)
{
struct mtk_nand_chip_info *info = &data_info->chip_info;
int i;
open->max = info->plane_num;
open->array = kmalloc_array(open->max, sizeof(int), GFP_KERNEL);
if (open->array == NULL)
return -1;
for (i = 0; i < open->max; i++)
open->array[i] = -1;
spin_lock_init(&open->lock);
open->ws = wakeup_source_register(NULL, "nand");
return 0;
}
static int open_block_count(const struct open_block *open)
{
int i, count = 0;
for (i = 0; i < open->max; i++) {
if (open->array[i] != -1)
count++;
}
return count;
}
static int open_block_delete(struct open_block *open, int block)
{
int i, count;
int ret;
spin_lock(&open->lock);
count = open_block_count(open);
for (i = 0; i < open->max; i++) {
if (open->array[i] == block) {
open->array[i] = -1;
if (count == 1) {
__pm_relax(open->ws);
pr_debug("pm_relax\n");
}
ret = 0;
break;
}
}
if (i == open->max)
ret = -1;
spin_unlock(&open->lock);
if (ret)
pr_err("fail!!!\n");
return ret;
}
static bool should_close_block(void)
{
return false;
}
static u32 mtk_nand_do_erase(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, int count)
{
struct list_node *item;
int i, status, op_cnt, ret = 0;
int plane_num, odd = 0;
u32 *rows;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
u32 block_num = 0, mode = 0;
bool multi_plane = false;
nandx_get_device(FL_ERASING);
i = 0;
item = list_ctrl->head.next;
if (!count)
goto err;
if (count != 1 && count % info->plane_num) {
pr_err("count:%d\n", count);
dump_stack();
goto err;
}
if (item == NULL) {
pr_err("head next null, count:%d\n", count);
dump_stack();
goto err;
}
rows = mem_alloc(1, sizeof(u32) * count);
if (!rows) {
dump_stack();
goto err;
}
ret = count;
multi_plane = is_multi_plane(info);
while ((i < count) && item) {
work = get_list_work(item);
ops = &work->ops;
block_num = ops->block + data_info->bmt.start_block;
rows[i] = block_num * info->data_page_num + ops->page;
pr_debug("%s: ops_block %d, page %d, row %d\n",
__func__, ops->block, ops->page, rows[i]);
item = item->next;
if (multi_plane) {
if (!(i & 1))
odd = ops->block & 1;
else if ((ops->block & 1) == odd)
multi_plane = false;
}
i++;
}
/* check whether is slc block */
block_num -= data_info->bmt.start_block;
if (is_slc_block(info, block_num))
mode |= MODE_SLC;
/* check whether support multi-plane */
if (i & 1)
multi_plane = false;
else if (multi_plane)
mode |= MODE_MULTI;
status = nandx_core_erase(rows, count, mode);
i = 0;
item = list_ctrl->head.next;
plane_num = multi_plane ? info->plane_num : 1;
while ((i < count) && item) {
op_cnt = ((count - i) > plane_num) ? plane_num : (count - i);
pr_debug(" i:%d, op_cnt:%d\n", i, op_cnt);
call_multi_work_callback(item, op_cnt, status);
item = free_multi_erase_work(list_ctrl, item, op_cnt);
i += op_cnt;
}
kfree(rows);
pr_debug("%s done\n", __func__);
err:
nandx_release_device();
return ret;
}
static u32 mtk_nand_do_slc_write(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, int count)
{
int i, status, odd = 0, ret = 0;
struct nand_work *work;
struct list_node *item;
struct mtk_nand_chip_operation *ops;
u32 block_num, mode = 0;
bool multi_plane = false;
nandx_get_device(FL_WRITING);
if (!count)
goto err;
if (count != 1 && count != 2) {
pr_err("count not 1 or 2, count:%d\n", count);
goto err;
}
item = list_ctrl->head.next;
ret = count;
multi_plane = is_multi_plane(info);
if (multi_plane && (count % info->plane_num) > 0)
multi_plane = false;
for (i = 0; i < count; i++) {
work = get_list_work(item);
ops = &work->ops;
item = item->next;
block_num = ops->block + data_info->bmt.start_block;
if (multi_plane) {
if (!(i & 1))
odd = block_num & 1;
else if ((block_num & 1) == odd)
multi_plane = false;
}
ops_table[i].row =
block_num * info->data_page_num + ops->page;
ops_table[i].col = 0;
ops_table[i].len = info->log_page_size;
ops_table[i].data = ops->data_buffer;
ops_table[i].oob = ops->oob_buffer;
}
mode |= MODE_SLC;
if (multi_plane)
mode |= MODE_MULTI;
status = nandx_core_write(ops_table, count, mode);
if (status == -ENANDWRITE) {
ret = callback_and_free_ewrite(list_ctrl, count);
goto err;
}
call_multi_work_callback(list_ctrl->head.next, count, status);
free_multi_write_work(list_ctrl, list_ctrl->head.next, count);
err:
nandx_release_device();
return ret;
}
static u32 mtk_nand_do_write(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl, int count)
{
struct list_node *node;
node = list_ctrl->head.next;
if (info->types == MTK_NAND_FLASH_MLC)
return do_mlc_multi_plane_write(info, list_ctrl, node, count);
if (info->types == MTK_NAND_FLASH_TLC)
return do_tlc_write(info, list_ctrl, node, count);
pr_err("unhandled work!\n");
return 0;
}
static int mtk_nand_process_list(struct mtk_nand_chip_info *info,
struct worklist_ctrl *list_ctrl,
int sync_num)
{
u32 total, ready_cnt;
u32 process_cnt = 0, left_cnt;
mutex_lock(&list_ctrl->sync_lock);
total = get_list_work_cnt(list_ctrl);
if (((sync_num > 0) && (total < sync_num)) || (total == 0))
goto out;
left_cnt = total - process_cnt;
do {
ready_cnt = list_ctrl->get_ready_count_func(info, list_ctrl,
left_cnt);
if (!ready_cnt)
break;
process_cnt += list_ctrl->process_data_func(info, list_ctrl,
ready_cnt);
left_cnt = total - process_cnt;
} while (left_cnt);
if ((sync_num == -1) && left_cnt) {
/* only force sync erase and slc write work */
if (list_ctrl->type == LIST_NS_WRITE)
goto out;
left_cnt = total - process_cnt;
if (left_cnt > 1)
pr_err("left_cnt:%d\n", left_cnt);
list_ctrl->process_data_func(info, list_ctrl, 1);
process_cnt += 1;
}
out:
mutex_unlock(&list_ctrl->sync_lock);
return process_cnt;
}
static int mtk_nand_work_thread(void *u)
{
struct mtk_nand_chip_info *info = &data_info->chip_info;
struct worklist_ctrl *elist_ctrl = &data_info->elist_ctrl;
struct worklist_ctrl *swlist_ctrl = &data_info->swlist_ctrl;
struct worklist_ctrl *wlist_ctrl = &data_info->wlist_ctrl;
pr_debug("%s started, PID %d\n", __func__, task_pid_nr(current));
for (;;) {
wait_for_completion(&data_info->ops_ctrl);
mtk_nand_process_list(info, elist_ctrl, 0);
mtk_nand_process_list(info, swlist_ctrl, 0);
mtk_nand_process_list(info, wlist_ctrl, 0);
}
pr_debug("%s exit, PID %d\n", __func__, task_pid_nr(current));
return 0;
}
static void mtk_nand_dump_bbt_info(struct mtk_nand_chip_bbt_info *chip_bbt)
{
u32 i;
pr_info("%s: bad_block_num:%d, initial_bad_num:%d\n",
__func__, chip_bbt->bad_block_num, chip_bbt->initial_bad_num);
for (i = 0; i < chip_bbt->bad_block_num; i++)
pr_info("bad_index:%d\n", chip_bbt->bad_block_table[i]);
}
int mtk_chip_bbt_init(struct data_bmt_struct *data_bmt)
{
struct mtk_nand_chip_bbt_info *chip_bbt = &data_info->chip_bbt;
u32 i, initial_bad_num;
u32 initial_bad, ftl_mark_bad;
u16 bad_block;
chip_bbt->bad_block_num = data_bmt->bad_count;
chip_bbt->initial_bad_num = data_bmt->bad_count;
if (data_bmt->bad_count > BAD_BLOCK_MAX_NUM) {
pr_err("bad block count > max(%d)\n", BAD_BLOCK_MAX_NUM);
return -1;
}
initial_bad_num = 0;
for (i = 0; i < data_bmt->bad_count; i++)
if (data_bmt->entry[i].flag != FTL_MARK_BAD)
initial_bad_num++;
chip_bbt->initial_bad_num = initial_bad_num;
initial_bad = 0;
ftl_mark_bad = initial_bad_num;
for (i = 0; i < data_bmt->bad_count; i++) {
bad_block = data_bmt->entry[i].bad_index -
data_bmt->start_block;
if (data_bmt->entry[i].flag != FTL_MARK_BAD) {
chip_bbt->bad_block_table[initial_bad] = bad_block;
initial_bad++;
} else {
chip_bbt->bad_block_table[ftl_mark_bad] = bad_block;
ftl_mark_bad++;
}
}
for (i = data_bmt->bad_count; i < BAD_BLOCK_MAX_NUM; i++)
chip_bbt->bad_block_table[i] = 0xffff;
mtk_nand_dump_bbt_info(chip_bbt);
return 0;
}
static int mtk_chip_info_init(struct mtk_nand_chip_info *chip_info)
{
struct nandx_chip_info *info;
u32 page_per_block;
info = get_chip_info();
page_per_block = info->block_size / info->page_size;
chip_info->data_block_num = data_info->partition_info.total_block *
(100 - data_info->partition_info.slc_ratio) / 100;
chip_info->log_block_num = data_info->partition_info.total_block -
chip_info->data_block_num;
chip_info->data_page_num = page_per_block;
chip_info->data_page_size = info->page_size;
chip_info->data_block_size = info->block_size;
chip_info->log_page_size = info->page_size;
chip_info->log_page_num = page_per_block / info->wl_page_num;
chip_info->log_block_size = info->block_size / info->wl_page_num;
if (info->plane_num > 1) {
chip_info->data_block_num >>= 1;
chip_info->data_block_num <<= 1;
chip_info->log_block_num >>= 1;
chip_info->log_block_num <<= 1;
}
chip_info->data_oob_size = info->oob_size;
chip_info->log_oob_size = chip_info->data_oob_size;
chip_info->slc_ratio = data_info->partition_info.slc_ratio;
chip_info->start_block = data_info->partition_info.start_block;
chip_info->sector_size_shift = 10;
if (info->wl_page_num == 3)
chip_info->max_keep_pages = (info->plane_num > 1) ? 18 : 9;
else if (info->wl_page_num == 2)
chip_info->max_keep_pages = (info->plane_num > 1) ? 4 : 2;
else
chip_info->max_keep_pages = (info->plane_num > 1) ? 2 : 1;
if (info->wl_page_num == 3)
chip_info->types = MTK_NAND_FLASH_TLC;
else if (info->wl_page_num == 2)
chip_info->types = MTK_NAND_FLASH_MLC;
else
chip_info->types = MTK_NAND_FLASH_SLC;
chip_info->plane_mask = 0x1;
chip_info->plane_num = (info->plane_num > 1) ? 2 : 1;
chip_info->chip_num = 1;
if (info->plane_num > 1)
chip_info->option = MTK_NAND_PLANE_MODE_SUPPORT |
MTK_NAND_MULTI_READ_DIFFERENT_OFFSET;
/* Todo: export PE cycle from device info!! */
chip_info->data_pe = 2000;
chip_info->log_pe = 25000;
chip_info->info_version = MTK_NAND_CHIP_INFO_VERSION;
return 0;
}
static void mtk_nand_dump_chip_info(struct mtk_nand_chip_info *chip_info)
{
pr_debug("mtk_nand_chip_info dump info here\n");
pr_debug("data_block_num: %d\n", chip_info->data_block_num);
pr_debug("data_page_num: %d\n", chip_info->data_page_num);
pr_debug("data_page_size: %d\n", chip_info->data_page_size);
pr_debug("data_oob_size: %d\n", chip_info->data_oob_size);
pr_debug("data_block_size: 0x%x\n", chip_info->data_block_size);
pr_debug("log_block_num: %d\n", chip_info->log_block_num);
pr_debug("log_page_num: %d\n", chip_info->log_page_num);
pr_debug("log_page_size: %d\n", chip_info->log_page_size);
pr_debug("log_block_size: 0x%x\n", chip_info->log_block_size);
pr_debug("log_oob_size: %d\n", chip_info->log_oob_size);
pr_debug("slc_ratio: %d\n", chip_info->slc_ratio);
pr_debug("start_block: %d\n", chip_info->start_block);
pr_debug("sector_size_shift: %d\n", chip_info->sector_size_shift);
pr_debug("max_keep_pages: %d\n", chip_info->max_keep_pages);
pr_debug("types: 0x%x\n", chip_info->types);
pr_debug("plane_mask: 0x%x\n", chip_info->plane_mask);
pr_debug("plane_num: %d\n", chip_info->plane_num);
pr_debug("chip_num: %d\n", chip_info->chip_num);
pr_debug("option: 0x%x\n", chip_info->option);
}
static void mtk_nand_dump_partition_info(struct nand_ftl_partition_info
*partition_info)
{
pr_debug("nand_ftl_partition_info dump:\n");
pr_debug("start_block: %d\n", partition_info->start_block);
pr_debug("total_block: %d\n", partition_info->total_block);
pr_debug("slc_ratio: %d\n", partition_info->slc_ratio);
pr_debug("slc_block: %d\n", partition_info->slc_block);
}
static void mtk_nand_dump_bmt_info(struct data_bmt_struct *data_bmt)
{
u32 i;
pr_debug("nand_ftl_bmt_info dump:\n");
pr_debug("bad_count: %d\n", data_bmt->bad_count);
pr_debug("start_block: %d\n", data_bmt->start_block);
pr_debug("end_block: %d\n", data_bmt->end_block);
pr_debug("version: %d\n", data_bmt->version);
for (i = 0; i < data_bmt->bad_count; i++) {
pr_debug("bad_index: %d, flag: %d\n",
data_bmt->entry[i].bad_index,
data_bmt->entry[i].flag);
}
}
/* API for nand Wrapper */
/*
* struct mtk_nand_chip_info *mtk_nand_chip_init(void)
* Init mntl_chip_info to nand wrapper, after nand driver init.
* Return: On success, return mtk_nand_chip_info. On error, return error num.
*/
struct mtk_nand_chip_info *mtk_nand_chip_init(void)
{
if (&data_info->chip_info != NULL)
return &data_info->chip_info;
else
return 0;
}
EXPORT_SYMBOL(mtk_nand_chip_init);
/*
* mtk_nand_chip_read_page
* Only one page data and FDM data read, support partial read.
*
* @info: NAND handler
* @data_buffer/oob_buffer: Null for no need data/oob
* must contiguous address space.
* @block/page: The block/page to read data.
* @offset: data offset to start read, and must be aligned to sector size.
* @size: data size to read. size <= pagesize
* less than page size will partial read, and OOB is only related
* sectors, uncompleted refer to whole page.
* return : 0 on success, On error, return error num.
*/
int mtk_nand_chip_read_page(struct mtk_nand_chip_info *info,
u8 *data_buffer, u8 *oob_buffer, u32 block,
u32 page, u32 offset, u32 size)
{
int ret = 0;
if (data_buffer == NULL) {
pr_err("data_buffer is null\n");
return -EINVAL;
}
if (oob_buffer == NULL) {
pr_err("oob_buffer is null\n");
return -EINVAL;
}
if ((offset % (1 << info->sector_size_shift) != 0)
|| (size % (1 << info->sector_size_shift) != 0)) {
pr_err("offset or size is invalid:offset:%d, size:%d\n",
offset, size);
return -EINVAL;
}
if (!block_page_num_is_valid(info, block, page)) {
pr_err("%s: block or page is invalid:block:%d, page:%d\n",
__func__, block, page);
dump_block_bitmap();
dump_stack();
return -EINVAL;
}
if (mtk_isbad_block(block))
return -ENANDBAD;
ret = mtk_nand_read_pages(info, data_buffer,
oob_buffer, block, page, offset, size);
if (ret) {
pr_err("read err:%d block:%d page:%d offset:%d size:%d\n",
ret, block, page, offset, size);
}
return ret;
}
EXPORT_SYMBOL(mtk_nand_chip_read_page);
/*
* mtk_nand_chip_read_pages
* Read multiple pages of data/FDM at once. Support partial read.
*
* The driver can choose the number of pages it actually read.
* Driver only guarantee to read at least one page. Caller must handle
* unread pages by itself.
*
* If there are any error in 2nd or following pages, just return numbers
* of page read without any error. Driver shouldn't retry/re-read other pages.
*
* @info: NAND handler
* @page_num: the page numbers to read.
* @param: parameters for each page read
* return : >0 number of pages read without any error (including ENANDFLIPS)
* On first page read error, return error number.
*/
int mtk_nand_chip_read_multi_pages(struct mtk_nand_chip_info *info,
int page_num,
struct mtk_nand_chip_read_param *param)
{
struct mtk_nand_chip_read_param *p = param;
int ret;
if (!page_num) {
pr_err("page_num is 0, so return 0\n");
dump_stack();
return 0;
}
if (page_num == 1 || !is_multi_read_support(info)) {
ret = mtk_nand_chip_read_page(info, p->data_buffer,
p->oob_buffer, p->block,
p->page, p->offset, p->size);
return ret ? ret : 1;
}
if (!can_multi_plane(p->block, p->page, p[1].block, p[1].page)) {
ret = mtk_nand_chip_read_page(info, p->data_buffer,
p->oob_buffer, p->block,
p->page, p->offset, p->size);
return ret ? ret : 1;
}
return mtk_nand_read_multi_pages(info, 2, param);
}
EXPORT_SYMBOL(mtk_nand_chip_read_multi_pages);
/*
* mtk_nand_chip_write_page
* write page API. Only one page data write, async mode.
* Just return 0 and add to write worklist as below:
* a) For TLC WL write, NAND handler call nand driver WL write function.
* b) For Multi-plane program, if more_page is TRUE,
* wait for the next pages write and do multi-plane write on time.
* c) For cache program, driver will depend on more_page flag for TLC program,
* can not used for SLC/MLC program.
* after Nand driver erase/write operation, callback function will be done.
*
* @info: NAND handler
* @data_buffer/oob_buffer: must contiguous address space.
* @block/page: The block/page to write data.
* @more_page: for TLC WL write and multi-plane program operation.
* if more_page is true, driver will wait complete
* operation and call driver function.
* @*callback: callback for wrapper, called after driver finish the operation.
* @*userdata : for callback function
* return : 0 on success, On error, return error num casted by ERR_PTR
*/
int mtk_nand_chip_write_page(struct mtk_nand_chip_info *info,
u8 *data_buffer, u8 *oob_buffer,
u32 block, u32 page, bool more_page,
mtk_nand_callback_func callback, void *userdata)
{
/* Add to worklist here */
struct worklist_ctrl *list_ctrl;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
int total_num;
u32 max_keep_pages;
int page_num;
int ret;
if (data_buffer == NULL) {
pr_err("data_buffer is null\n");
return -EINVAL;
}
if (oob_buffer == NULL) {
pr_err("oob_buffer is null\n");
return -EINVAL;
}
if (!block_page_num_is_valid(info, block, page)) {
pr_err("%s: block or page is invalid:block:%d, page:%d\n",
__func__, block, page);
dump_block_bitmap();
return -EINVAL;
}
list_ctrl = (block < info->data_block_num) ?
&data_info->wlist_ctrl : &data_info->swlist_ctrl;
total_num = get_list_work_cnt(list_ctrl);
max_keep_pages = is_slc_block(info, block) ?
info->plane_num : info->max_keep_pages;
while (total_num >= max_keep_pages) {
mtk_nand_process_list(info, list_ctrl, max_keep_pages);
total_num = get_list_work_cnt(list_ctrl);
};
work = kmalloc(sizeof(struct nand_work), GFP_KERNEL);
if (work == NULL)
return -ENOMEM;
work->list.next = NULL;
ops = &work->ops;
ops->info = info;
ops->types = MTK_NAND_OP_WRITE;
ops->data_buffer = data_buffer;
ops->oob_buffer = oob_buffer;
ops->block = block;
ops->page = page;
ops->more = more_page;
ops->callback = callback;
ops->userdata = userdata;
ret = add_list_node(list_ctrl, &work->list);
if (ret)
return 0;
page_num = is_slc_block(info, block) ? info->log_page_num :
info->data_page_num;
total_num = get_list_work_cnt(list_ctrl);
if (total_num >= max_keep_pages || (page == page_num - 1))
complete(&data_info->ops_ctrl);
return 0;
}
EXPORT_SYMBOL(mtk_nand_chip_write_page);
/*
* mtk_nand_chip_erase_block
* Erase API for nand wrapper, async mode for erase, just return success,
* put erase operation into write worklist.
* After Nand driver erase/write operation, callback function will be done.
* @block: The block to erase
* @*callback: Callback for wrapper, called after driver finish the operation.
* @* userdata: for callback function
* return : 0 on success, On error, return error num casted by ERR_PTR
*/
int mtk_nand_chip_erase_block(struct mtk_nand_chip_info *info,
u32 block, u32 more_block,
mtk_nand_callback_func callback, void *userdata)
{
struct worklist_ctrl *list_ctrl;
struct nand_work *work;
struct mtk_nand_chip_operation *ops;
int total_num;
pr_debug("erase block:%d more_page:%d\n", block, more_block);
if (!block_num_is_valid(info, block)) {
pr_err("block num is invalid:block:%d\n", block);
dump_block_bitmap();
return -EINVAL;
}
list_ctrl = &data_info->elist_ctrl;
total_num = get_list_work_cnt(list_ctrl);
while (total_num >= info->max_keep_pages) {
mtk_nand_process_list(info, list_ctrl, info->max_keep_pages);
list_ctrl = &data_info->elist_ctrl;
total_num = get_list_work_cnt(list_ctrl);
};
work = kmalloc(sizeof(struct nand_work), GFP_KERNEL);
if (work == NULL)
return -ENOMEM;
work->list.next = NULL;
ops = &work->ops;
ops->info = info;
ops->types = MTK_NAND_OP_ERASE;
ops->block = block;
ops->page = 0;
ops->more = more_block;
ops->data_buffer = NULL;
ops->oob_buffer = NULL;
ops->callback = callback;
ops->userdata = userdata;
list_ctrl = &data_info->elist_ctrl;
add_list_node(list_ctrl, &work->list);
total_num = get_list_work_cnt(list_ctrl);
if (total_num >= info->plane_num)
complete(&data_info->ops_ctrl);
return 0;
}
EXPORT_SYMBOL(mtk_nand_chip_erase_block);
/*
* mtk_nand_chip_sync
* flush all async worklist to nand driver.
* return : On success, return 0. On error, return error num
*/
int mtk_nand_chip_sync(struct mtk_nand_chip_info *info)
{
struct worklist_ctrl *elist_ctrl, *swlist_ctrl, *wlist_ctrl;
int ret = 0;
elist_ctrl = &data_info->elist_ctrl;
swlist_ctrl = &data_info->swlist_ctrl;
wlist_ctrl = &data_info->wlist_ctrl;
ret = mtk_nand_process_list(info, elist_ctrl, -1);
ret |= mtk_nand_process_list(info, swlist_ctrl, -1);
ret |= mtk_nand_process_list(info, wlist_ctrl, -1);
return ret;
}
EXPORT_SYMBOL(mtk_nand_chip_sync);
void mtk_nand_chip_set_blk_thread(struct task_struct *thead)
{
data_info->blk_thread = thead;
}
EXPORT_SYMBOL(mtk_nand_chip_set_blk_thread);
/*
* mtk_nand_chip_bmt, bad block table maintained by driver,
* and read only for wrapper
* @info: NAND handler
* Return FTL partition's bad block table for nand wrapper.
*/
const struct mtk_nand_chip_bbt_info *mtk_nand_chip_bmt(struct
mtk_nand_chip_info
*info)
{
if (&data_info->chip_bbt != NULL)
return &data_info->chip_bbt;
else
return 0;
}
EXPORT_SYMBOL(mtk_nand_chip_bmt);
/*
* mtk_chip_mark_bad_block
* Mark specific block as bad block, update bad block list and bad block table.
* @block: block address to markbad
*/
void mtk_chip_mark_bad_block(struct mtk_nand_chip_info *info, u32 block)
{
struct mtk_nand_chip_bbt_info *chip_bbt = &data_info->chip_bbt;
u32 i;
bool success;
pr_err("markbad block:%d\n", block);
for (i = 0; i < chip_bbt->bad_block_num; i++) {
if (block == chip_bbt->bad_block_table[i])
return;
}
chip_bbt->bad_block_table[chip_bbt->bad_block_num++] = block;
success = nandx_bmt_update(block + data_info->bmt.start_block,
FTL_MARK_BAD);
if (!success)
pr_err("mark block(%d) as bad fail!!!\n", block);
}
EXPORT_SYMBOL(mtk_chip_mark_bad_block);
int nandx_mntl_data_info_alloc(void)
{
data_info = mem_alloc(1, sizeof(struct mtk_nand_data_info));
if (data_info == NULL)
return -ENOMEM;
return 0;
}
void nandx_mntl_data_info_free(void)
{
mem_free(data_info);
}
int nandx_mntl_ops_init(void)
{
int ret = 0;
struct mtk_nand_chip_info *info;
info = &data_info->chip_info;
ret = get_data_partition_info(&data_info->partition_info,
&data_info->chip_info);
if (ret) {
pr_err("Get FTL partition info failed\n");
goto err_out;
}
mtk_nand_dump_partition_info(&data_info->partition_info);
ret = nandx_bmt_get_data_bmt(&data_info->bmt);
if (ret) {
pr_err("Get FTL bmt info failed\n");
goto err_out;
}
mtk_nand_dump_bmt_info(&data_info->bmt);
ret = mtk_chip_info_init(info);
if (ret) {
pr_err("Get chip info failed\n");
goto err_out;
}
mtk_nand_dump_chip_info(info);
ret = open_block_init(&data_info->open);
if (ret)
goto err_out;
ret = mtk_chip_bbt_init(&data_info->bmt);
if (ret) {
pr_err("Get chip bbt info failed\n");
goto err_out;
}
ops_table = alloc_ops_table(info->max_keep_pages);
if (!ops_table) {
pr_err("error:alloc ops table failed!\n");
goto err_out;
}
init_list_ctrl(info, &data_info->elist_ctrl, LIST_ERASE,
complete_erase_count, mtk_nand_do_erase);
init_list_ctrl(info, &data_info->swlist_ctrl, LIST_SLC_WRITE,
complete_slc_write_count, mtk_nand_do_slc_write);
init_list_ctrl(info, &data_info->wlist_ctrl, LIST_NS_WRITE,
complete_write_count, mtk_nand_do_write);
init_completion(&data_info->ops_ctrl);
data_info->nand_bgt = kthread_run(mtk_nand_work_thread,
data_info, "nand_bgt");
if (IS_ERR(data_info->nand_bgt)) {
ret = PTR_ERR(data_info->nand_bgt);
data_info->nand_bgt = NULL;
pr_err("kthread_create failed error %d\n", ret);
goto err_out;
}
return 0;
err_out:
return ret;
}
void mtk_nand_update_call_trace(unsigned int *address, char type,
unsigned int block, unsigned int page)
{
#ifdef __REPLAY_CALL__
if (!mntl_record_en)
return;
if (call_idx == 0) {
call_trace[call_idx].call_address = address;
call_trace[call_idx].op_type = type;
call_trace[call_idx].block = block;
call_trace[call_idx].page = page;
call_trace[call_idx].times++;
call_idx = (call_idx + 1) % 4096;
} else {
if (call_trace[call_idx - 1].op_type == type &&
call_trace[call_idx - 1].block == block) {
call_trace[call_idx - 1].times++;
} else {
call_trace[call_idx].call_address = address;
call_trace[call_idx].op_type = type;
call_trace[call_idx].block = block;
call_trace[call_idx].page = page;
call_trace[call_idx].times++;
call_idx = (call_idx + 1) % 4096;
}
}
#endif
}
EXPORT_SYMBOL(mtk_nand_update_call_trace);
static void dump_block_bitmap(void)
{
int i, num;
struct mtk_nand_chip_info *info = &data_info->chip_info;
num = info->data_block_num + info->log_block_num;
num = div_up(num, 32) * info->plane_num;
for (i = 0; i < num; i++) {
pr_info("%08x ", info->block_type_bitmap[i]);
if ((i + 1) % 16 == 0)
pr_info("\n");
}
pr_info("\n");
}
#if 0
void dump_block_bit_map(u8 *array)
{
int i, j;
int num;
bool check;
pr_info("%s\n", __func__);
for (i = 0; i < 500; i++) {
num = array[i];
if (num == 0)
continue;
for (j = 0; j < 8; j++) {
if ((num & (1 << j)) == 0)
continue;
check = is_slc_block(&data_info->chip_info,
((i * 8) + j));
pr_info("block %d is tlc check = %d\n",
((i * 8) + j), check);
}
}
}
#endif
int mtk_nand_update_block_type(int num, unsigned int *blk)
{
struct mtk_nand_chip_info *info = &data_info->chip_info;
u32 row;
int i, ret;
nandx_get_device(FL_ERASING);
for (i = 0; i < num; i++) {
pr_info("%s: erase block %d\n", __func__, blk[i]);
row = (blk[i] + data_info->bmt.start_block) *
info->data_page_num;
ret = nandx_core_erase(&row, 1, MODE_SLC);
if (ret)
pr_err("erase block %d failed\n", blk[i]);
}
nandx_release_device();
return mntl_update_part_tab(&data_info->chip_info, num, blk);
}
EXPORT_SYMBOL(mtk_nand_update_block_type);
int os_mvg_enabled(void)
{
#if defined(CONFIG_PWR_LOSS_MTK_SPOH)
return 1;
#else
return 0;
#endif
}
EXPORT_SYMBOL(os_mvg_enabled);
int init_case_trigger;
int os_mvg_on_group_case(const char *gname, const char *cname)
{
#if defined(CONFIG_PWR_LOSS_MTK_SPOH)
int trigger = mvg_trigger_get();
if (trigger != 0 && trigger != -1)
init_case_trigger = trigger;
if (mvg_on_group_case(gname, cname)) {
if (mvg_trigger())
mvg_set_trigger(-1);
} else {
mvg_set_trigger(init_case_trigger);
}
#endif
return 0;
}
EXPORT_SYMBOL(os_mvg_on_group_case);
#if defined(CONFIG_PWR_LOSS_MTK_SPOH)
static void push_case(const char *gname, const char *cname)
{
struct mvg_case_stack *case_stack;
case_stack = kmalloc(sizeof(struct mvg_case_stack), GFP_KERNEL);
strcpy(case_stack->gname, gname);
strcpy(case_stack->cname, cname);
case_stack->next = cstack;
cstack = case_stack;
}
static void pop_case(void)
{
struct mvg_case_stack *case_stack = cstack;
cstack = cstack->next;
kfree(case_stack);
}
int mvg_current_case_check(void)
{
if (cstack)
return mvg_on_group_case(cstack->gname, cstack->cname);
return 0;
}
#endif
int mvg_set_current_case(const char *gname, const char *cname)
{
#if defined(CONFIG_PWR_LOSS_MTK_SPOH)
if (mvg_on_group_case(gname, cname)) {
if (cstack) {
pr_info("[MVG] set case reentry on %s-%s\n",
gname, cname);
return -EINVAL;
}
push_case(gname, cname);
} else {
if (cstack)
push_case(gname, cname);
}
#endif
return 0;
}
EXPORT_SYMBOL(mvg_set_current_case);
int mvg_case_exit(const char *gname, const char *cname)
{
#if defined(CONFIG_PWR_LOSS_MTK_SPOH)
int s1, s2;
if (!cstack)
return 0;
s1 = strcmp(cstack->gname, gname);
s2 = strcmp(cstack->cname, cname);
if (s1 || s2) {
pr_info("[MVG] %s: un-balance current %s-%s, exit %s-%s\n",
__func__, cstack->gname, cstack->cname, gname, cname);
return -EINVAL;
}
pop_case();
#endif
return 0;
}
EXPORT_SYMBOL(mvg_case_exit);
unsigned long long os_sched_clock(void)
{
return sched_clock();
}
EXPORT_SYMBOL(os_sched_clock);
/*
* mtk_is_hs
* Return 1 if it is in home screen now.
*/
int mtk_is_hs(void)
{
return g_i4Homescreen;
}
EXPORT_SYMBOL(mtk_is_hs);
void os_mutex_destroy(struct mutex *lock)
{
mutex_destroy(lock);
}
EXPORT_SYMBOL(os_mutex_destroy);
void __sched os_mutex_lock(struct mutex *lock)
{
mutex_lock(lock);
}
EXPORT_SYMBOL(os_mutex_lock);
void dump_nfi_op(void)
{
/* dump_record(); */
}
EXPORT_SYMBOL(dump_nfi_op);
int init_mntl_module(void)
{
void __iomem *va;
u64 base, size;
get_mntl_buf(&base, &size);
if (base == 0) {
pr_err("mntl mem buffer is NULL\n");
return -ENODEV;
}
va = ioremap_wc(base, size);
if (va == NULL) {
pr_err("remap ko memory fail\n");
return -ENODEV;
}
pr_info("load ko %p\n", va);
init_module_mem((void *)va, size);
return 0;
}