2007-10-12 08:04:54 +00:00
|
|
|
/*
|
|
|
|
* (C) Copyright 2005
|
|
|
|
* 2N Telekomunikace, a.s. <www.2n.cz>
|
|
|
|
* Ladislav Michl <michl@2n.cz>
|
|
|
|
*
|
|
|
|
* See file CREDITS for list of people who contributed to this
|
|
|
|
* project.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* version 2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*/
|
|
|
|
#include <common.h>
|
2007-10-19 23:13:46 +00:00
|
|
|
#include <linux/mtd/nand.h>
|
2008-06-04 07:40:44 +00:00
|
|
|
#include <linux/mtd/mtd.h>
|
2007-10-15 15:16:25 +00:00
|
|
|
#include <init.h>
|
|
|
|
#include <xfuncs.h>
|
|
|
|
#include <driver.h>
|
2008-06-04 07:40:44 +00:00
|
|
|
#include <malloc.h>
|
|
|
|
#include <ioctl.h>
|
|
|
|
#include <nand.h>
|
2009-10-20 13:28:16 +00:00
|
|
|
#include <errno.h>
|
2013-07-10 07:27:06 +00:00
|
|
|
#include <of.h>
|
2007-10-12 08:04:54 +00:00
|
|
|
|
2011-12-21 21:30:42 +00:00
|
|
|
#include "mtd.h"
|
|
|
|
|
|
|
|
static LIST_HEAD(mtd_register_hooks);
|
|
|
|
|
2013-02-20 10:19:28 +00:00
|
|
|
int mtd_all_ff(const void *buf, unsigned int len)
|
|
|
|
{
|
|
|
|
while ((unsigned long)buf & 0x3) {
|
|
|
|
if (*(const uint8_t *)buf != 0xff)
|
|
|
|
return 0;
|
|
|
|
len--;
|
|
|
|
if (!len)
|
|
|
|
return 1;
|
|
|
|
buf++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (len > 0x3) {
|
|
|
|
if (*(const uint32_t *)buf != 0xffffffff)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
len -= sizeof(uint32_t);
|
|
|
|
if (!len)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
buf += sizeof(uint32_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (len) {
|
|
|
|
if (*(const uint8_t *)buf != 0xff)
|
|
|
|
return 0;
|
|
|
|
len--;
|
|
|
|
buf++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-02-14 09:22:15 +00:00
|
|
|
static ssize_t mtd_op_read(struct cdev *cdev, void* buf, size_t count,
|
2011-10-14 11:46:09 +00:00
|
|
|
loff_t _offset, ulong flags)
|
2007-10-15 15:16:25 +00:00
|
|
|
{
|
2011-12-21 21:30:40 +00:00
|
|
|
struct mtd_info *mtd = cdev->priv;
|
2007-10-19 23:13:46 +00:00
|
|
|
size_t retlen;
|
|
|
|
int ret;
|
2011-10-14 11:46:09 +00:00
|
|
|
unsigned long offset = _offset;
|
2007-10-12 08:04:54 +00:00
|
|
|
|
2013-06-21 06:12:54 +00:00
|
|
|
dev_dbg(cdev->dev, "read ofs: 0x%08lx count: 0x%08zx\n",
|
2013-02-14 09:22:15 +00:00
|
|
|
offset, count);
|
2007-10-12 08:04:54 +00:00
|
|
|
|
2013-02-14 09:38:20 +00:00
|
|
|
ret = mtd_read(mtd, offset, count, &retlen, buf);
|
2013-12-19 15:17:40 +00:00
|
|
|
if (ret < 0)
|
2007-10-19 23:13:46 +00:00
|
|
|
return ret;
|
2013-12-19 15:17:40 +00:00
|
|
|
if (mtd->ecc_strength == 0)
|
|
|
|
return retlen; /* device lacks ecc */
|
|
|
|
return ret >= mtd->bitflip_threshold ? -EUCLEAN : retlen;
|
2007-10-12 08:04:54 +00:00
|
|
|
}
|
|
|
|
|
2011-12-21 21:30:40 +00:00
|
|
|
#define NOTALIGNED(x) (x & (mtd->writesize - 1)) != 0
|
2011-12-31 12:56:04 +00:00
|
|
|
#define MTDPGALG(x) ((x) & ~(mtd->writesize - 1))
|
2008-06-04 07:40:44 +00:00
|
|
|
|
2011-12-21 21:30:40 +00:00
|
|
|
#ifdef CONFIG_MTD_WRITE
|
2013-02-14 09:22:15 +00:00
|
|
|
static ssize_t mtd_op_write(struct cdev* cdev, const void *buf, size_t _count,
|
2011-10-14 11:46:09 +00:00
|
|
|
loff_t _offset, ulong flags)
|
2007-10-12 08:04:54 +00:00
|
|
|
{
|
2011-12-21 21:30:40 +00:00
|
|
|
struct mtd_info *mtd = cdev->priv;
|
2012-11-29 10:16:40 +00:00
|
|
|
size_t retlen;
|
|
|
|
int ret;
|
2008-06-04 07:40:44 +00:00
|
|
|
|
2013-02-14 09:38:20 +00:00
|
|
|
ret = mtd_write(mtd, _offset, _count, &retlen, buf);
|
2008-06-04 07:40:44 +00:00
|
|
|
|
|
|
|
return ret ? ret : _count;
|
|
|
|
}
|
2012-12-17 15:48:28 +00:00
|
|
|
|
2013-06-06 09:44:10 +00:00
|
|
|
static struct mtd_erase_region_info *mtd_find_erase_region(struct mtd_info *mtd, loff_t offset)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < mtd->numeraseregions; i++) {
|
|
|
|
struct mtd_erase_region_info *e = &mtd->eraseregions[i];
|
|
|
|
if (offset > e->offset + e->erasesize * e->numblocks)
|
|
|
|
continue;
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mtd_erase_align(struct mtd_info *mtd, size_t *count, loff_t *offset)
|
|
|
|
{
|
|
|
|
struct mtd_erase_region_info *e;
|
|
|
|
loff_t ofs;
|
|
|
|
|
|
|
|
if (mtd->numeraseregions == 0) {
|
|
|
|
ofs = *offset & ~(mtd->erasesize - 1);
|
|
|
|
*count += (*offset - ofs);
|
|
|
|
*count = ALIGN(*count, mtd->erasesize);
|
|
|
|
*offset = ofs;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
e = mtd_find_erase_region(mtd, *offset);
|
|
|
|
if (!e)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ofs = *offset & ~(e->erasesize - 1);
|
|
|
|
*count += (*offset - ofs);
|
|
|
|
|
|
|
|
e = mtd_find_erase_region(mtd, *offset + *count);
|
|
|
|
if (!e)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
*count = ALIGN(*count, e->erasesize);
|
|
|
|
*offset = ofs;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-14 09:22:15 +00:00
|
|
|
static int mtd_op_erase(struct cdev *cdev, size_t count, loff_t offset)
|
2012-12-17 15:48:28 +00:00
|
|
|
{
|
|
|
|
struct mtd_info *mtd = cdev->priv;
|
|
|
|
struct erase_info erase;
|
2014-01-14 11:26:28 +00:00
|
|
|
uint32_t addr;
|
2012-12-17 15:48:28 +00:00
|
|
|
int ret;
|
|
|
|
|
2013-06-06 09:44:10 +00:00
|
|
|
ret = mtd_erase_align(mtd, &count, &offset);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2012-12-17 15:48:28 +00:00
|
|
|
memset(&erase, 0, sizeof(erase));
|
|
|
|
erase.mtd = mtd;
|
2014-01-14 11:26:28 +00:00
|
|
|
addr = offset;
|
2013-05-24 22:16:31 +00:00
|
|
|
|
|
|
|
if (!mtd->block_isbad) {
|
2014-01-14 11:26:28 +00:00
|
|
|
erase.addr = addr;
|
2013-05-24 22:16:31 +00:00
|
|
|
erase.len = count;
|
|
|
|
return mtd_erase(mtd, &erase);
|
|
|
|
}
|
|
|
|
|
2012-12-17 15:48:28 +00:00
|
|
|
erase.len = mtd->erasesize;
|
|
|
|
|
|
|
|
while (count > 0) {
|
2014-01-14 11:26:28 +00:00
|
|
|
dev_dbg(cdev->dev, "erase %d %d\n", addr, erase.len);
|
2012-12-17 15:48:28 +00:00
|
|
|
|
2013-02-27 10:01:17 +00:00
|
|
|
if (!mtd->allow_erasebad)
|
2014-01-14 11:26:28 +00:00
|
|
|
ret = mtd_block_isbad(mtd, addr);
|
2013-02-27 10:01:17 +00:00
|
|
|
else
|
|
|
|
ret = 0;
|
|
|
|
|
2014-01-14 11:26:28 +00:00
|
|
|
erase.addr = addr;
|
|
|
|
|
2012-12-17 15:48:28 +00:00
|
|
|
if (ret > 0) {
|
2014-01-14 11:26:28 +00:00
|
|
|
printf("Skipping bad block at 0x%08x\n", addr);
|
2012-12-17 15:48:28 +00:00
|
|
|
} else {
|
2013-02-14 09:38:20 +00:00
|
|
|
ret = mtd_erase(mtd, &erase);
|
2012-12-17 15:48:28 +00:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-01-14 11:26:28 +00:00
|
|
|
addr += mtd->erasesize;
|
2012-12-17 15:48:28 +00:00
|
|
|
count -= count > mtd->erasesize ? mtd->erasesize : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2012-04-19 18:12:56 +00:00
|
|
|
|
|
|
|
static ssize_t mtd_op_protect(struct cdev *cdev, size_t count, loff_t offset, int prot)
|
|
|
|
{
|
|
|
|
struct mtd_info *mtd = cdev->priv;
|
|
|
|
|
|
|
|
if (!mtd->unlock || !mtd->lock)
|
|
|
|
return -ENOSYS;
|
|
|
|
|
|
|
|
if (prot)
|
|
|
|
return mtd_lock(mtd, offset, count);
|
|
|
|
else
|
|
|
|
return mtd_unlock(mtd, offset, count);
|
|
|
|
}
|
|
|
|
|
2012-12-17 15:48:28 +00:00
|
|
|
#endif /* CONFIG_MTD_WRITE */
|
2008-06-04 07:40:44 +00:00
|
|
|
|
2011-12-21 21:30:43 +00:00
|
|
|
int mtd_ioctl(struct cdev *cdev, int request, void *buf)
|
2008-06-04 07:40:44 +00:00
|
|
|
{
|
2012-01-05 11:03:29 +00:00
|
|
|
int ret = 0;
|
2011-12-21 21:30:40 +00:00
|
|
|
struct mtd_info *mtd = cdev->priv;
|
2008-08-11 08:59:28 +00:00
|
|
|
struct mtd_info_user *user = buf;
|
2012-04-05 06:48:11 +00:00
|
|
|
#if (defined(CONFIG_NAND_ECC_HW) || defined(CONFIG_NAND_ECC_SOFT))
|
2012-01-05 11:03:29 +00:00
|
|
|
struct mtd_ecc_stats *ecc = buf;
|
2012-04-05 06:48:11 +00:00
|
|
|
#endif
|
2012-01-05 11:03:29 +00:00
|
|
|
struct region_info_user *reg = buf;
|
2013-01-16 21:06:35 +00:00
|
|
|
#ifdef CONFIG_MTD_WRITE
|
2012-12-17 15:48:28 +00:00
|
|
|
struct erase_info_user *ei = buf;
|
2013-01-16 21:06:35 +00:00
|
|
|
#endif
|
2011-10-14 11:46:09 +00:00
|
|
|
loff_t *offset = buf;
|
2008-06-04 07:40:44 +00:00
|
|
|
|
|
|
|
switch (request) {
|
|
|
|
case MEMGETBADBLOCK:
|
2011-10-14 11:46:09 +00:00
|
|
|
dev_dbg(cdev->dev, "MEMGETBADBLOCK: 0x%08llx\n", *offset);
|
2012-11-01 09:33:14 +00:00
|
|
|
ret = mtd_block_isbad(mtd, *offset);
|
2012-01-05 11:03:29 +00:00
|
|
|
break;
|
2011-12-21 21:30:40 +00:00
|
|
|
#ifdef CONFIG_MTD_WRITE
|
2008-09-02 15:20:33 +00:00
|
|
|
case MEMSETBADBLOCK:
|
2011-10-14 11:46:09 +00:00
|
|
|
dev_dbg(cdev->dev, "MEMSETBADBLOCK: 0x%08llx\n", *offset);
|
2013-02-14 09:38:20 +00:00
|
|
|
ret = mtd_block_markbad(mtd, *offset);
|
2012-01-05 11:03:29 +00:00
|
|
|
break;
|
2012-12-17 15:48:28 +00:00
|
|
|
case MEMERASE:
|
2013-02-14 09:22:15 +00:00
|
|
|
ret = mtd_op_erase(cdev, ei->length, ei->start + cdev->offset);
|
2012-12-17 15:48:28 +00:00
|
|
|
break;
|
2011-04-07 15:04:32 +00:00
|
|
|
#endif
|
2008-06-04 07:40:44 +00:00
|
|
|
case MEMGETINFO:
|
2011-12-21 21:30:40 +00:00
|
|
|
user->type = mtd->type;
|
|
|
|
user->flags = mtd->flags;
|
|
|
|
user->size = mtd->size;
|
|
|
|
user->erasesize = mtd->erasesize;
|
2012-10-13 07:45:28 +00:00
|
|
|
user->writesize = mtd->writesize;
|
2011-12-21 21:30:40 +00:00
|
|
|
user->oobsize = mtd->oobsize;
|
2013-12-20 12:00:10 +00:00
|
|
|
user->subpagesize = mtd->writesize >> mtd->subpage_sft;
|
2011-12-21 21:30:40 +00:00
|
|
|
user->mtd = mtd;
|
2008-06-04 07:40:44 +00:00
|
|
|
/* The below fields are obsolete */
|
2008-08-11 08:59:28 +00:00
|
|
|
user->ecctype = -1;
|
|
|
|
user->eccsize = 0;
|
2012-01-05 11:03:29 +00:00
|
|
|
break;
|
|
|
|
#if (defined(CONFIG_NAND_ECC_HW) || defined(CONFIG_NAND_ECC_SOFT))
|
|
|
|
case ECCGETSTATS:
|
|
|
|
ecc->corrected = mtd->ecc_stats.corrected;
|
|
|
|
ecc->failed = mtd->ecc_stats.failed;
|
|
|
|
ecc->badblocks = mtd->ecc_stats.badblocks;
|
|
|
|
ecc->bbtblocks = mtd->ecc_stats.bbtblocks;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
case MEMGETREGIONINFO:
|
|
|
|
if (cdev->mtd) {
|
2011-10-14 11:46:09 +00:00
|
|
|
unsigned long size = cdev->size;
|
2012-01-05 11:03:29 +00:00
|
|
|
reg->offset = cdev->offset;
|
|
|
|
reg->erasesize = cdev->mtd->erasesize;
|
2011-10-14 11:46:09 +00:00
|
|
|
reg->numblocks = size / reg->erasesize;
|
2012-01-05 11:03:29 +00:00
|
|
|
reg->regionindex = cdev->mtd->index;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
2008-06-04 07:40:44 +00:00
|
|
|
}
|
|
|
|
|
2012-01-05 11:03:29 +00:00
|
|
|
return ret;
|
2008-06-04 07:40:44 +00:00
|
|
|
}
|
|
|
|
|
2012-04-19 18:12:56 +00:00
|
|
|
int mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|
|
|
{
|
|
|
|
if (!mtd->lock)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs)
|
|
|
|
return -EINVAL;
|
|
|
|
if (!len)
|
|
|
|
return 0;
|
|
|
|
return mtd->lock(mtd, ofs, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|
|
|
{
|
|
|
|
if (!mtd->unlock)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
if (ofs < 0 || ofs > mtd->size || len > mtd->size - ofs)
|
|
|
|
return -EINVAL;
|
|
|
|
if (!len)
|
|
|
|
return 0;
|
|
|
|
return mtd->unlock(mtd, ofs, len);
|
|
|
|
}
|
|
|
|
|
2013-02-14 09:31:37 +00:00
|
|
|
int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs)
|
|
|
|
{
|
|
|
|
if (!mtd->block_isbad)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (ofs < 0 || ofs > mtd->size)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return mtd->block_isbad(mtd, ofs);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (mtd->block_markbad)
|
|
|
|
ret = mtd->block_markbad(mtd, ofs);
|
|
|
|
else
|
|
|
|
ret = -ENOSYS;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
|
|
|
|
u_char *buf)
|
|
|
|
{
|
2013-12-20 12:47:42 +00:00
|
|
|
int ret_code;
|
|
|
|
*retlen = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In the absence of an error, drivers return a non-negative integer
|
|
|
|
* representing the maximum number of bitflips that were corrected on
|
|
|
|
* any one ecc region (if applicable; zero otherwise).
|
|
|
|
*/
|
|
|
|
ret_code = mtd->read(mtd, from, len, retlen, buf);
|
|
|
|
if (unlikely(ret_code < 0))
|
|
|
|
return ret_code;
|
|
|
|
if (mtd->ecc_strength == 0)
|
|
|
|
return 0; /* device lacks ecc */
|
|
|
|
return ret_code >= mtd->bitflip_threshold ? -EUCLEAN : 0;
|
2013-02-14 09:31:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
|
|
|
const u_char *buf)
|
|
|
|
{
|
|
|
|
return mtd->write(mtd, to, len, retlen, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
int mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|
|
|
{
|
|
|
|
return mtd->erase(mtd, instr);
|
|
|
|
}
|
|
|
|
|
2013-07-18 13:07:42 +00:00
|
|
|
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
|
|
|
|
{
|
|
|
|
int ret_code;
|
|
|
|
|
|
|
|
ops->retlen = ops->oobretlen = 0;
|
|
|
|
if (!mtd->read_oob)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
/*
|
|
|
|
* In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics
|
|
|
|
* similar to mtd->_read(), returning a non-negative integer
|
|
|
|
* representing max bitflips. In other cases, mtd->_read_oob() may
|
|
|
|
* return -EUCLEAN. In all cases, perform similar logic to mtd_read().
|
|
|
|
*/
|
|
|
|
ret_code = mtd->read_oob(mtd, from, ops);
|
|
|
|
if (unlikely(ret_code < 0))
|
|
|
|
return ret_code;
|
|
|
|
if (mtd->ecc_strength == 0)
|
|
|
|
return 0; /* device lacks ecc */
|
|
|
|
return ret_code >= mtd->bitflip_threshold ? -EUCLEAN : 0;
|
|
|
|
}
|
|
|
|
|
2011-12-21 21:30:40 +00:00
|
|
|
static struct file_operations mtd_ops = {
|
2013-02-14 09:22:15 +00:00
|
|
|
.read = mtd_op_read,
|
2011-12-21 21:30:40 +00:00
|
|
|
#ifdef CONFIG_MTD_WRITE
|
2013-02-14 09:22:15 +00:00
|
|
|
.write = mtd_op_write,
|
|
|
|
.erase = mtd_op_erase,
|
2012-04-19 18:12:56 +00:00
|
|
|
.protect = mtd_op_protect,
|
2011-04-04 09:24:47 +00:00
|
|
|
#endif
|
2011-12-21 21:30:40 +00:00
|
|
|
.ioctl = mtd_ioctl,
|
2009-04-22 21:39:27 +00:00
|
|
|
.lseek = dev_lseek_default,
|
|
|
|
};
|
|
|
|
|
2013-10-28 11:45:56 +00:00
|
|
|
int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
|
2011-04-07 15:59:52 +00:00
|
|
|
{
|
2011-12-21 21:30:42 +00:00
|
|
|
struct mtddev_hook *hook;
|
2009-10-20 13:28:16 +00:00
|
|
|
|
2011-12-21 21:30:40 +00:00
|
|
|
if (!devname)
|
|
|
|
devname = "mtd";
|
|
|
|
strcpy(mtd->class_dev.name, devname);
|
2013-10-28 11:45:56 +00:00
|
|
|
mtd->class_dev.id = device_id;
|
2012-12-12 13:55:40 +00:00
|
|
|
if (mtd->parent)
|
2012-10-16 15:25:37 +00:00
|
|
|
mtd->class_dev.parent = mtd->parent;
|
2012-10-16 15:25:36 +00:00
|
|
|
register_device(&mtd->class_dev);
|
2008-08-11 08:59:28 +00:00
|
|
|
|
2011-12-21 21:30:40 +00:00
|
|
|
mtd->cdev.ops = &mtd_ops;
|
2009-04-22 21:39:27 +00:00
|
|
|
mtd->cdev.size = mtd->size;
|
2013-10-28 11:45:56 +00:00
|
|
|
if (device_id == DEVICE_ID_SINGLE)
|
|
|
|
mtd->cdev.name = xstrdup(devname);
|
|
|
|
else
|
|
|
|
mtd->cdev.name = asprintf("%s%d", devname, mtd->class_dev.id);
|
|
|
|
|
2009-04-22 21:39:27 +00:00
|
|
|
mtd->cdev.priv = mtd;
|
2009-06-11 13:33:49 +00:00
|
|
|
mtd->cdev.dev = &mtd->class_dev;
|
2010-06-28 08:21:57 +00:00
|
|
|
mtd->cdev.mtd = mtd;
|
2008-08-11 08:59:28 +00:00
|
|
|
|
2012-01-08 17:25:18 +00:00
|
|
|
if (IS_ENABLED(CONFIG_PARAMETER)) {
|
2014-06-12 09:37:38 +00:00
|
|
|
dev_add_param_llint_ro(&mtd->class_dev, "size", mtd->size, "%llu");
|
2013-04-06 07:01:57 +00:00
|
|
|
dev_add_param_int_ro(&mtd->class_dev, "erasesize", mtd->erasesize, "%u");
|
2014-06-09 15:14:07 +00:00
|
|
|
dev_add_param_int_ro(&mtd->class_dev, "writesize", mtd->writesize, "%u");
|
2013-04-06 07:01:57 +00:00
|
|
|
dev_add_param_int_ro(&mtd->class_dev, "oobsize", mtd->oobsize, "%u");
|
2012-01-08 17:25:18 +00:00
|
|
|
}
|
2009-07-21 16:37:32 +00:00
|
|
|
|
2009-04-22 21:39:27 +00:00
|
|
|
devfs_create(&mtd->cdev);
|
2014-01-14 09:27:10 +00:00
|
|
|
|
2014-04-09 14:03:52 +00:00
|
|
|
if (mtd_can_have_bb(mtd))
|
|
|
|
mtd->cdev_bb = mtd_add_bb(mtd, NULL);
|
|
|
|
|
2014-01-14 10:51:35 +00:00
|
|
|
if (mtd->parent && !mtd->master)
|
2014-01-14 09:27:10 +00:00
|
|
|
of_parse_partitions(&mtd->cdev, mtd->parent->device_node);
|
2008-08-11 08:59:28 +00:00
|
|
|
|
2011-12-21 21:30:42 +00:00
|
|
|
list_for_each_entry(hook, &mtd_register_hooks, hook)
|
|
|
|
if (hook->add_mtd_device)
|
2012-09-03 05:58:01 +00:00
|
|
|
hook->add_mtd_device(mtd, devname, &hook->priv);
|
2009-10-20 13:28:16 +00:00
|
|
|
|
2008-08-11 08:59:28 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int del_mtd_device (struct mtd_info *mtd)
|
|
|
|
{
|
2011-12-21 21:30:42 +00:00
|
|
|
struct mtddev_hook *hook;
|
|
|
|
|
|
|
|
list_for_each_entry(hook, &mtd_register_hooks, hook)
|
|
|
|
if (hook->del_mtd_device)
|
2012-09-03 05:58:01 +00:00
|
|
|
hook->del_mtd_device(mtd, &hook->priv);
|
|
|
|
|
|
|
|
devfs_remove(&mtd->cdev);
|
2009-04-22 21:39:27 +00:00
|
|
|
unregister_device(&mtd->class_dev);
|
2009-07-21 16:37:32 +00:00
|
|
|
free(mtd->param_size.value);
|
2009-12-21 09:43:50 +00:00
|
|
|
free(mtd->cdev.name);
|
2008-08-11 08:59:28 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-12-21 21:30:42 +00:00
|
|
|
void mtdcore_add_hook(struct mtddev_hook *hook)
|
|
|
|
{
|
|
|
|
list_add(&hook->hook, &mtd_register_hooks);
|
|
|
|
}
|