/* * 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, "engcall") == 0) { nand_dbg_err("NAND DEBUG: %s\n", cmd); /* gc with invalid_page_count equal to (page_per_block / 2) */ ret = gc_all_enhance(nftl_blk->nftl_zone); if (!ret) return count; else return -EIO; } 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; }