453 lines
12 KiB
C
453 lines
12 KiB
C
/*
|
|
* nand_class.c for SUNXI NAND .
|
|
*
|
|
* Copyright (C) 2016 Allwinner.
|
|
*
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#define BLK_INFO_MSG_ON
|
|
#include "nand_class.h"
|
|
|
|
struct nand_kobject *nand_debug_kobj;
|
|
|
|
static ssize_t nand_sysfs_show(struct kobject *kobject, struct attribute *attr,
|
|
char *buf);
|
|
static ssize_t nand_sysfs_store(struct kobject *kobject, struct attribute *attr,
|
|
const char *buf, size_t count);
|
|
static ssize_t nand_show_debug(struct _nftl_blk *nftl_blk, char *buf);
|
|
static ssize_t nand_store_debug(struct _nftl_blk *nftl_blk, const char *buf,
|
|
size_t count);
|
|
static ssize_t nand_show_arch(struct _nftl_blk *nftl_blk, char *buf);
|
|
static ssize_t nand_show_gcinfo(struct _nftl_blk *nftl_blk, char *buf);
|
|
static ssize_t nand_show_version(struct _nftl_blk *nftl_blk, char *buf);
|
|
static ssize_t nand_show_badblk(struct _nftl_blk *nftl_blk, char *buf);
|
|
static ssize_t nand_store_gcone(struct _nftl_blk *nftl_blk, const char *buf,
|
|
size_t count);
|
|
static ssize_t nand_store_gcall(struct _nftl_blk *nftl_blk, const char *buf,
|
|
size_t count);
|
|
|
|
static struct attribute attr_debug = {
|
|
.name = "nand_debug",
|
|
.mode = S_IRUGO | S_IWUSR | S_IWGRP,
|
|
};
|
|
|
|
static struct attribute attr_arch = {
|
|
.name = "arch",
|
|
.mode = S_IRUGO,
|
|
};
|
|
|
|
static struct attribute attr_gcinfo = {
|
|
.name = "gcinfo",
|
|
.mode = S_IRUGO,
|
|
};
|
|
|
|
static struct attribute attr_badblk = {
|
|
.name = "badblock",
|
|
.mode = S_IRUGO,
|
|
};
|
|
|
|
static struct attribute attr_gc_all = {
|
|
.name = "gc_all",
|
|
.mode = S_IWUSR | S_IWGRP,
|
|
};
|
|
|
|
static struct attribute attr_gc_one = {
|
|
.name = "gc_one",
|
|
.mode = S_IWUSR | S_IWGRP,
|
|
};
|
|
|
|
static struct attribute attr_version = {
|
|
.name = "version",
|
|
.mode = S_IRUGO,
|
|
};
|
|
|
|
static struct attribute *sysfs_attrs[] = {
|
|
&attr_debug,
|
|
&attr_arch,
|
|
&attr_gcinfo,
|
|
&attr_badblk,
|
|
&attr_gc_all,
|
|
&attr_gc_one,
|
|
&attr_version,
|
|
NULL,
|
|
};
|
|
|
|
static const struct sysfs_ops sysfs_ops = {
|
|
.show = nand_sysfs_show,
|
|
.store = nand_sysfs_store,
|
|
};
|
|
|
|
static struct kobj_type sysfs_type = {
|
|
.sysfs_ops = &sysfs_ops,
|
|
.default_attrs = sysfs_attrs,
|
|
};
|
|
|
|
int nand_debug_init(struct _nftl_blk *nftl_blk, int part_num)
|
|
{
|
|
int ret;
|
|
|
|
nand_debug_kobj = kzalloc(sizeof(struct nand_kobject), GFP_KERNEL);
|
|
if (!nand_debug_kobj) {
|
|
nand_dbg_err("kzalloc failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
nand_debug_kobj->nftl_blk = nftl_blk;
|
|
ret = kobject_init_and_add(&nand_debug_kobj->kobj, &sysfs_type, NULL,
|
|
"nand_driver%d", part_num);
|
|
if (ret) {
|
|
nand_dbg_err("init nand sysfs fail!\n");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct nand_attr_ops {
|
|
struct attribute *attr;
|
|
ssize_t (*show)(struct _nftl_blk *blk, char *buf);
|
|
ssize_t (*store)(struct _nftl_blk *blk, const char *buf, size_t cnt);
|
|
};
|
|
|
|
static struct nand_attr_ops attr_ops_array[] = {
|
|
{
|
|
.attr = &attr_debug,
|
|
.show = nand_show_debug,
|
|
.store = nand_store_debug,
|
|
},
|
|
{
|
|
.attr = &attr_arch,
|
|
.show = nand_show_arch,
|
|
},
|
|
{
|
|
.attr = &attr_gcinfo,
|
|
.show = nand_show_gcinfo,
|
|
},
|
|
{
|
|
.attr = &attr_version,
|
|
.show = nand_show_version,
|
|
},
|
|
{
|
|
.attr = &attr_badblk,
|
|
.show = nand_show_badblk,
|
|
},
|
|
{
|
|
.attr = &attr_gc_all,
|
|
.store = nand_store_gcall,
|
|
},
|
|
{
|
|
.attr = &attr_gc_one,
|
|
.store = nand_store_gcone,
|
|
},
|
|
};
|
|
|
|
static ssize_t nand_sysfs_show(struct kobject *kobj, struct attribute *attr,
|
|
char *buf)
|
|
{
|
|
int index;
|
|
struct nand_attr_ops *attr_ops;
|
|
struct _nftl_blk *nftl_blk = ((struct nand_kobject *)kobj)->nftl_blk;
|
|
ssize_t ret;
|
|
|
|
for (index = 0; index < ARRAY_SIZE(attr_ops_array); index++) {
|
|
attr_ops = &attr_ops_array[index];
|
|
if (attr == attr_ops->attr)
|
|
break;
|
|
}
|
|
if (unlikely(index == ARRAY_SIZE(attr_ops_array))) {
|
|
nand_dbg_err("not found attr_ops for %s\n", attr->name);
|
|
return -EINVAL;
|
|
}
|
|
if (attr_ops->show) {
|
|
mutex_lock(nftl_blk->blk_lock);
|
|
ret = attr_ops->show(nftl_blk, buf);
|
|
mutex_unlock(nftl_blk->blk_lock);
|
|
return ret;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static ssize_t nand_sysfs_store(struct kobject *kobj, struct attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int index;
|
|
struct nand_attr_ops *attr_ops;
|
|
struct _nftl_blk *nftl_blk = ((struct nand_kobject *)kobj)->nftl_blk;
|
|
ssize_t ret;
|
|
|
|
for (index = 0; index < ARRAY_SIZE(attr_ops_array); index++) {
|
|
attr_ops = &attr_ops_array[index];
|
|
if (attr == attr_ops->attr)
|
|
break;
|
|
}
|
|
if (unlikely(index == ARRAY_SIZE(attr_ops_array))) {
|
|
nand_dbg_err("not found attr_ops for %s\n", attr->name);
|
|
return -EINVAL;
|
|
}
|
|
if (attr_ops->store) {
|
|
mutex_lock(nftl_blk->blk_lock);
|
|
ret = attr_ops->store(nftl_blk, buf, count);
|
|
mutex_unlock(nftl_blk->blk_lock);
|
|
return ret;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static ssize_t nand_show_debug(struct _nftl_blk *nftl_blk, char *buf)
|
|
{
|
|
ssize_t ret = 0;
|
|
int tmp[4] = {0};
|
|
|
|
ret += PHY_GetArchInfo_Str(buf);
|
|
ret += sprintf(buf + ret, "BadBlkCnt: %d\n",
|
|
nftl_get_bad_block_cnt(nftl_blk));
|
|
|
|
NAND_Get_Version(&tmp[0], &tmp[1], &tmp[2], &tmp[3]);
|
|
ret += sprintf(buf + ret, "NandVersion: %x.%x %x %x\n",
|
|
tmp[0], tmp[1], tmp[2], tmp[3]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t nand_store_debug(struct _nftl_blk *nftl_blk, const char *buf,
|
|
size_t count)
|
|
{
|
|
int ret, i;
|
|
int arg_num = 0;
|
|
char cmd[32] = { 0 };
|
|
unsigned int arg0_int = 0;
|
|
unsigned int arg1_int = 0;
|
|
unsigned int arg2_int = 0;
|
|
char arg3_str[16] = { 0 };
|
|
|
|
arg_num = sscanf(buf, "%31s %u %u %u %15s", cmd, &arg0_int, &arg1_int,
|
|
&arg2_int, arg3_str);
|
|
if (-1 == arg_num || 0 == arg_num) {
|
|
nand_dbg_err("cmd format err!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (strcmp(cmd, "flush") == 0) {
|
|
if (2 != arg_num) {
|
|
nand_dbg_err("err: %s needs 1 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u\n", cmd, arg0_int);
|
|
ret = nftl_blk->flush_write_cache(nftl_blk, arg0_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "gcall") == 0) {
|
|
if (2 != arg_num) {
|
|
nand_dbg_err("err: %s needs 1 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u\n", cmd, arg0_int);
|
|
ret = gc_all(nftl_blk->nftl_zone, arg0_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "gcone") == 0) {
|
|
if (2 != arg_num) {
|
|
nand_dbg_err("err: %s needs 1 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u\n", cmd, arg0_int);
|
|
ret = gc_one(nftl_blk->nftl_zone, arg0_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "priogc") == 0) {
|
|
if (3 != arg_num) {
|
|
nand_dbg_err("err: %s needs 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u\n", cmd, arg0_int, arg1_int);
|
|
ret = prio_gc_one(nftl_blk->nftl_zone, arg0_int,
|
|
arg1_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "test") == 0) {
|
|
if (2 != arg_num) {
|
|
nand_dbg_err("err: %s needs 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u\n", cmd, arg0_int);
|
|
ret = nftl_set_zone_test((void *)nftl_blk->nftl_zone,
|
|
arg0_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "showall") == 0) {
|
|
nand_dbg_err("NAND DEBUG: %s\n", cmd);
|
|
print_free_list(nftl_blk->nftl_zone);
|
|
print_block_invalid_list(nftl_blk->nftl_zone);
|
|
return count;
|
|
} else if (strcmp(cmd, "showinfo") == 0) {
|
|
nand_dbg_err("NAND DEBUG: %s\n", cmd);
|
|
print_nftl_zone(nftl_blk->nftl_zone);
|
|
return count;
|
|
} else if (strcmp(cmd, "blkdebug") == 0) {
|
|
if (2 != arg_num) {
|
|
nand_dbg_err("err: %s needs 1 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u\n", cmd, arg0_int);
|
|
debug_data = arg0_int;
|
|
return count;
|
|
} else if (strcmp(cmd, "smart") == 0) {
|
|
nand_dbg_err("NAND DEBUG: %s\n", cmd);
|
|
print_smart(nftl_blk->nftl_zone);
|
|
return count;
|
|
} else if (strcmp(cmd, "read1") == 0) {
|
|
if (4 != arg_num) {
|
|
nand_dbg_err("err: %s needs 3 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u %u\n", cmd, arg0_int,
|
|
arg1_int, arg2_int);
|
|
nand_dbg_phy_read(arg0_int, arg1_int, arg2_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "read2") == 0) {
|
|
if (3 != arg_num) {
|
|
nand_dbg_err(" %s need 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u\n", cmd, arg0_int, arg1_int);
|
|
nand_dbg_zone_phy_read(nftl_blk->nftl_zone, arg0_int,
|
|
arg1_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "erase1") == 0) {
|
|
if (3 != arg_num) {
|
|
nand_dbg_err("err: %s needs 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u\n", cmd, arg0_int, arg1_int);
|
|
nand_dbg_phy_erase(arg0_int, arg1_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "erase2") == 0) {
|
|
if (3 != arg_num) {
|
|
nand_dbg_err("err: %s needs 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u\n", cmd, arg0_int, arg1_int);
|
|
nand_dbg_zone_erase(nftl_blk->nftl_zone, arg0_int,
|
|
arg1_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "erase3") == 0) {
|
|
if (3 != arg_num) {
|
|
nand_dbg_err("err: %s needs 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u\n", cmd, arg0_int, arg1_int);
|
|
nand_dbg_single_phy_erase(arg0_int, arg1_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "write1") == 0) {
|
|
if (4 != arg_num) {
|
|
nand_dbg_err("err: %s needs 3 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u %u\n", cmd, arg0_int,
|
|
arg1_int, arg2_int);
|
|
nand_dbg_phy_write(arg0_int, arg1_int, arg2_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "write2") == 0) {
|
|
if (3 != arg_num) {
|
|
nand_dbg_err(" %s need 2 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u\n", cmd, arg0_int, arg1_int);
|
|
nand_dbg_zone_phy_write(nftl_blk->nftl_zone, arg0_int,
|
|
arg1_int);
|
|
return count;
|
|
} else if (strcmp(cmd, "checktable") == 0) {
|
|
nand_dbg_err("NAND DEBUG: %s\n", cmd);
|
|
nand_check_table(nftl_blk->nftl_zone);
|
|
return count;
|
|
} else if (strcmp(cmd, "readdev") == 0) {
|
|
char *tempbuf;
|
|
|
|
if (5 != arg_num) {
|
|
nand_dbg_err("err: %s needs 4 argument\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
nand_dbg_err("NAND DEBUG: %s %u %u %u %s\n", cmd, arg0_int,
|
|
arg1_int, arg2_int, arg3_str);
|
|
if (arg1_int > 16) {
|
|
nand_dbg_err("arg1: max len is 16!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tempbuf = kmalloc(8192, GFP_KERNEL);
|
|
if (!tempbuf)
|
|
return PTR_ERR(tempbuf);
|
|
_dev_nand_read2(arg3_str, arg0_int, arg1_int, tempbuf);
|
|
for (i = 0; i < (arg1_int << 9); i += 4) {
|
|
nand_dbg_inf("%8x ", *((int *)&tempbuf[i]));
|
|
if (((i + 4) % 64) == 0)
|
|
nand_dbg_inf("\n");
|
|
}
|
|
kfree(tempbuf);
|
|
return count;
|
|
} else {
|
|
nand_dbg_err("NAND DEBUG: undefined cmd: %s\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nand_show_arch(struct _nftl_blk *nftl_blk, char *buf)
|
|
{
|
|
return PHY_GetArchInfo_Str(buf);
|
|
}
|
|
|
|
static ssize_t nand_show_gcinfo(struct _nftl_blk *nftl_blk, char *buf)
|
|
{
|
|
return nftl_get_gc_info(nftl_blk->nftl_zone, buf, 4096);
|
|
}
|
|
|
|
static ssize_t nand_show_version(struct _nftl_blk *nftl_blk, char *buf)
|
|
{
|
|
int tmp[4] = {0};
|
|
NAND_Get_Version(&tmp[0], &tmp[1], &tmp[2], &tmp[3]);
|
|
return sprintf(buf, "%x.%x %x %x\n", tmp[0], tmp[1], tmp[2], tmp[3]);
|
|
}
|
|
|
|
static ssize_t nand_show_badblk(struct _nftl_blk *nftl_blk, char *buf)
|
|
{
|
|
return sprintf(buf, "cnt: %d\n", nftl_get_bad_block_cnt(nftl_blk));
|
|
}
|
|
|
|
static ssize_t nand_store_gcone(struct _nftl_blk *nftl_blk, const char *buf,
|
|
size_t count)
|
|
{
|
|
int ret;
|
|
int invalid_page_count;
|
|
|
|
ret = sscanf(buf, "%u", &invalid_page_count);
|
|
if (1 != ret) {
|
|
nand_dbg_err("invalid parameter %s\n", buf);
|
|
nand_dbg_err("please enter invalid_page_count for gcone\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nand_dbg_inf("ctl: gcone %u\n", invalid_page_count);
|
|
ret = gc_one(nftl_blk->nftl_zone, invalid_page_count);
|
|
if (ret == 1)
|
|
return -EIO;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nand_store_gcall(struct _nftl_blk *nftl_blk, const char *buf,
|
|
size_t count)
|
|
{
|
|
int ret;
|
|
int invalid_page_count;
|
|
|
|
ret = sscanf(buf, "%u", &invalid_page_count);
|
|
if (1 != ret) {
|
|
nand_dbg_err("invalid parameter %s\n", buf);
|
|
nand_dbg_err("please enter invalid_page_count for gcall\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nand_dbg_inf("ctl: gcall %u\n", invalid_page_count);
|
|
ret = gc_all(nftl_blk->nftl_zone, invalid_page_count);
|
|
if (ret == 1)
|
|
return -EIO;
|
|
return count;
|
|
}
|