aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/mediatek/files-5.10/drivers/mtd/mtk-snand/mtk-snand-mtd.c
blob: 7e5baf03691126b5a30a3bdabd03bdbf325052b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
pre { line-height: 125%; margin: 0; }
td.linenos pre { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
span.linenos { color: #000000; background-color: #f0f0f0; padding: 0 5px 0 5px; }
td.linenos pre.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding: 0 5px 0 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight { background: #ffffff; }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="Content-Style-Type" content="text/css">
<link rel="up" title="Petit FatFs" href="../00index_p.html">
<link rel="stylesheet" href="../css_e.css" type="text/css" media="screen" title="ELM Default">
<link rel="stylesheet" href="../css_p.css" type="text/css" media="screen" title="ELM Default">
<title>Petit FatFs - pf_write</title>
</head>

<body>

<div class="para">
<h2>pf_write</h2>
<p>The pf_write function writes data to the file.</p>
<pre>
FRESULT pf_write (
  const void* <span class="arg">buff</span>, <span class="c">/* [IN]  Pointer to the data to be written */</span>
  UINT <span class="arg">btw</span>,         <span class="c">/* [IN]  Number of bytes to write */</span>
  UINT* <span class="arg">bw</span>          <span class="c">/* [OUT] Pointer to the variable to return number of bytes written */</span>
);
</pre>
</div>

<div class="para">
<h4>Parameters</h4>
<dl class="par">
<dt>buff</dt>
<dd>Pointer to the data to be wtitten. A NULL specifies to finalize the current write operation.</dd>
<dt>btw</dt>
<dd>Number of bytes to write.</dd>
<dt>bw</dt>
<dd>Pointer to the variable to return number of bytes read.</dd>
</dl>
</div>


<div class="para">
<h4>Return Values</h4>
<dl class="ret">
<dt>FR_OK (0)</dt>
<dd>The function succeeded.</dd>
<dt>FR_DISK_ERR</dt>
<dd>The function failed due to a hard error in the disk function, write protected, a wrong FAT structure or an internal error.</dd>
<dt>FR_NOT_OPENED</dt>
<dd>The file has not been opened.</dd>
<dt>FR_NOT_ENABLED</dt>
<dd>The volume has not been mounted.</dd>
</dl>
</div>


<div class="para">
<h4>Description</h4>
<p>The write function has some restrictions listed below:</p>
<ul>
<li>Cannot create file. Only existing file can be written.</li>
<li>Cannot expand file size.</li>
<li>Cannot update time stamp of the file.</li>
<li>Write operation can start/stop on the sector boundary.</li>
<li>Read-only attribute of the file cannot block write operation.</li>
</ul>
<p>File write operation must be done in following sequence.</p>
<ol>
<li><tt>pf_lseek(ofs);</tt> read/write pointer must be moved to sector bundary prior to initiate write operation or it will be rounded-down to the sector boundary.</li>
<li><tt>pf_write(buff, btw, &amp;bw);</tt> Initiate write operation. Write first data to the file.</li>
<li><tt>pf_write(buff, btw, &amp;bw);</tt> Write next data. Any other file function cannot be used while a write operation is in progress.</li>
<li><tt>pf_write(0, 0, &amp;bw);</tt> Finalize the write operation. If read/write pointer is not on the sector boundary, left bytes in the sector will be filled with zero.</li>
</ol>
<p>The read/write pointer in the file system object advances in number of bytes written. After the function succeeded, <tt>*bw</tt> should be checked to detect end of file. In case of <tt>*bw &lt; btw</tt>, it means the read/write pointer reached end of file during the write operation. Once a write operation is initiated, it must be finalized or the written data can be lost.</p>
</div>

<div class="para">
<h4>QuickInfo</h4>
<p>Available when <tt>_USE_WRITE == 1</tt>.</p>
</div>

<div class="para">
<h4>References</h4>
<p><tt><a<
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
 *
 * Author: Weijie Gao <weijie.gao@mediatek.com>
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/wait.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of_platform.h>

#include "mtk-snand.h"
#include "mtk-snand-os.h"

struct mtk_snand_of_id {
	enum mtk_snand_soc soc;
};

struct mtk_snand_mtd {
	struct mtk_snand_plat_dev pdev;

	struct clk *nfi_clk;
	struct clk *pad_clk;
	struct clk *ecc_clk;

	void __iomem *nfi_regs;
	void __iomem *ecc_regs;

	int irq;

	bool quad_spi;
	enum mtk_snand_soc soc;

	struct mtd_info mtd;
	struct mtk_snand *snf;
	struct mtk_snand_chip_info cinfo;
	uint8_t *page_cache;
	struct mutex lock;
};

#define mtd_to_msm(mtd) container_of(mtd, struct mtk_snand_mtd, mtd)

static int mtk_snand_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
	u64 start_addr, end_addr;
	int ret;

	/* Do not allow write past end of device */
	if ((instr->addr + instr->len) > msm->cinfo.chipsize) {
		dev_err(msm->pdev.dev,
			"attempt to erase beyond end of device\n");
		return -EINVAL;
	}

	start_addr = instr->addr & (~mtd->erasesize_mask);
	end_addr = instr->addr + instr->len;
	if (end_addr & mtd->erasesize_mask) {
		end_addr = (end_addr + mtd->erasesize_mask) &
			   (~mtd->erasesize_mask);
	}

	mutex_lock(&msm->lock);

	while (start_addr < end_addr) {
		if (mtk_snand_block_isbad(msm->snf, start_addr)) {
			instr->fail_addr = start_addr;
			ret = -EIO;
			break;
		}

		ret = mtk_snand_erase_block(msm->snf, start_addr);
		if (ret) {
			instr->fail_addr = start_addr;
			break;
		}

		start_addr += mtd->erasesize;
	}

	mutex_unlock(&msm->lock);

	return ret;
}

static int mtk_snand_mtd_read_data(struct mtk_snand_mtd *msm, uint64_t addr,
				   struct mtd_oob_ops *ops)
{
	struct mtd_info *mtd = &msm->mtd;
	size_t len, ooblen, maxooblen, chklen;
	uint32_t col, ooboffs;
	uint8_t *datcache, *oobcache;
	bool ecc_failed = false, raw = ops->mode == MTD_OPS_RAW ? true : false;
	int ret, max_bitflips = 0;

	col = addr & mtd->writesize_mask;
	addr &= ~mtd->writesize_mask;
	maxooblen = mtd_oobavail(mtd, ops);
	ooboffs = ops->ooboffs;
	ooblen = ops->ooblen;
	len = ops->len;

	datcache = len ? msm->page_cache : NULL;
	oobcache = ooblen ? msm->page_cache + mtd->writesize : NULL;

	ops->oobretlen = 0;
	ops->retlen = 0;

	while (len || ooblen) {
		if (ops->mode == MTD_OPS_AUTO_OOB)
			ret = mtk_snand_read_page_auto_oob(msm->snf, addr,
				datcache, oobcache, maxooblen, NULL, raw);
		else
			ret = mtk_snand_read_page(msm->snf, addr, datcache,
				oobcache, raw);

		if (ret < 0 && ret != -EBADMSG)
			return ret;

		if (ret == -EBADMSG) {
			mtd->ecc_stats.failed++;
			ecc_failed = true;
		} else {
			mtd->ecc_stats.corrected += ret;
			max_bitflips = max_t(int, ret, max_bitflips);
		}

		if (len) {
			/* Move data */
			chklen = mtd->writesize - col;
			if (chklen > len)
				chklen = len;

			memcpy(ops->datbuf + ops->retlen, datcache + col,
			       chklen);
			len -= chklen;
			col = 0; /* (col + chklen) %  */
			ops->retlen += chklen;
		}

		if (ooblen) {
			/* Move oob */
			chklen = maxooblen - ooboffs;
			if (chklen > ooblen)
				chklen = ooblen;

			memcpy(ops->oobbuf + ops->oobretlen, oobcache + ooboffs,
			       chklen);
			ooblen -= chklen;
			ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */
			ops->oobretlen += chklen;
		}

		addr += mtd->writesize;
	}

	return ecc_failed ? -EBADMSG : max_bitflips;
}

static int mtk_snand_mtd_read_oob(struct mtd_info *mtd, loff_t from,
				  struct mtd_oob_ops *ops)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
	uint32_t maxooblen;
	int ret;

	if (!ops->oobbuf && !ops->datbuf) {
		if (ops->ooblen || ops->len)
			return -EINVAL;

		return 0;
	}

	switch (ops->mode) {
	case MTD_OPS_PLACE_OOB:
	case MTD_OPS_AUTO_OOB:
	case MTD_OPS_RAW:
		break;
	default:
		dev_err(msm->pdev.dev, "unsupported oob mode: %u\n", ops->mode);
		return -EINVAL;
	}

	maxooblen = mtd_oobavail(mtd, ops);

	/* Do not allow read past end of device */
	if (ops->datbuf && (from + ops->len) > msm->cinfo.chipsize) {
		dev_err(msm->pdev.dev,
			"attempt to read beyond end of device\n");
		return -EINVAL;
	}

	if (unlikely(ops->ooboffs >= maxooblen)) {
		dev_err(msm->pdev.dev, "attempt to start read outside oob\n");
		return -EINVAL;
	}

	if (unlikely(from >= msm->cinfo.chipsize ||
		     ops->ooboffs + ops->ooblen >
			     ((msm->cinfo.chipsize >> mtd->writesize_shift) -
			      (from >> mtd->writesize_shift)) *
				     maxooblen)) {
		dev_err(msm->pdev.dev,
			"attempt to read beyond end of device\n");
		return -EINVAL;
	}

	mutex_lock(&msm->lock);
	ret = mtk_snand_mtd_read_data(msm, from, ops);
	mutex_unlock(&msm->lock);

	return ret;
}

static int mtk_snand_mtd_write_data(struct mtk_snand_mtd *msm, uint64_t addr,
				    struct mtd_oob_ops *ops)
{
	struct mtd_info *mtd = &msm->mtd;
	size_t len, ooblen, maxooblen, chklen, oobwrlen;
	uint32_t col, ooboffs;
	uint8_t *datcache, *oobcache;
	bool raw = ops->mode == MTD_OPS_RAW ? true : false;
	int ret;

	col = addr & mtd->writesize_mask;
	addr &= ~mtd->writesize_mask;
	maxooblen = mtd_oobavail(mtd, ops);
	ooboffs = ops->ooboffs;
	ooblen = ops->ooblen;
	len = ops->len;

	datcache = len ? msm->page_cache : NULL;
	oobcache = ooblen ? msm->page_cache + mtd->writesize : NULL;

	ops->oobretlen = 0;
	ops->retlen = 0;

	while (len || ooblen) {
		if (len) {
			/* Move data */
			chklen = mtd->writesize - col;
			if (chklen > len)
				chklen = len;

			memset(datcache, 0xff, col);
			memcpy(datcache + col, ops->datbuf + ops->retlen,
			       chklen);
			memset(datcache + col + chklen, 0xff,
			       mtd->writesize - col - chklen);
			len -= chklen;
			col = 0; /* (col + chklen) %  */
			ops->retlen += chklen;
		}

		oobwrlen = 0;
		if (ooblen) {
			/* Move oob */
			chklen = maxooblen - ooboffs;
			if (chklen > ooblen)
				chklen = ooblen;

			memset(oobcache, 0xff, ooboffs);
			memcpy(oobcache + ooboffs,
			       ops->oobbuf + ops->oobretlen, chklen);
			memset(oobcache + ooboffs + chklen, 0xff,
			       mtd->oobsize - ooboffs - chklen);
			oobwrlen = chklen + ooboffs;
			ooblen -= chklen;
			ooboffs = 0; /* (ooboffs + chklen) % maxooblen; */
			ops->oobretlen += chklen;
		}

		if (ops->mode == MTD_OPS_AUTO_OOB)
			ret = mtk_snand_write_page_auto_oob(msm->snf, addr,
				datcache, oobcache, oobwrlen, NULL, raw);
		else
			ret = mtk_snand_write_page(msm->snf, addr, datcache,
				oobcache, raw);

		if (ret)
			return ret;

		addr += mtd->writesize;
	}

	return 0;
}

static int mtk_snand_mtd_write_oob(struct mtd_info *mtd, loff_t to,
				   struct mtd_oob_ops *ops)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
	uint32_t maxooblen;
	int ret;

	if (!ops->oobbuf && !ops->datbuf) {
		if (ops->ooblen || ops->len)
			return -EINVAL;

		return 0;
	}

	switch (ops->mode) {
	case MTD_OPS_PLACE_OOB:
	case MTD_OPS_AUTO_OOB:
	case MTD_OPS_RAW:
		break;
	default:
		dev_err(msm->pdev.dev, "unsupported oob mode: %u\n", ops->mode);
		return -EINVAL;
	}

	maxooblen = mtd_oobavail(mtd, ops);

	/* Do not allow write past end of device */
	if (ops->datbuf && (to + ops->len) > msm->cinfo.chipsize) {
		dev_err(msm->pdev.dev,
			"attempt to write beyond end of device\n");
		return -EINVAL;
	}

	if (unlikely(ops->ooboffs >= maxooblen)) {
		dev_err(msm->pdev.dev,
			"attempt to start write outside oob\n");
		return -EINVAL;
	}

	if (unlikely(to >= msm->cinfo.chipsize ||
		     ops->ooboffs + ops->ooblen >
			     ((msm->cinfo.chipsize >> mtd->writesize_shift) -
			      (to >> mtd->writesize_shift)) *
				     maxooblen)) {
		dev_err(msm->pdev.dev,
			"attempt to write beyond end of device\n");
		return -EINVAL;
	}

	mutex_lock(&msm->lock);
	ret = mtk_snand_mtd_write_data(msm, to, ops);
	mutex_unlock(&msm->lock);

	return ret;
}

static int mtk_snand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
	int ret;

	mutex_lock(&msm->lock);
	ret = mtk_snand_block_isbad(msm->snf, offs);
	mutex_unlock(&msm->lock);

	return ret;
}

static int mtk_snand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);
	int ret;

	mutex_lock(&msm->lock);
	ret = mtk_snand_block_markbad(msm->snf, offs);
	mutex_unlock(&msm->lock);

	return ret;
}

static int mtk_snand_ooblayout_ecc(struct mtd_info *mtd, int section,
				   struct mtd_oob_region *oobecc)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);

	if (section)
		return -ERANGE;

	oobecc->offset = msm->cinfo.fdm_size * msm->cinfo.num_sectors;
	oobecc->length = mtd->oobsize - oobecc->offset;

	return 0;
}

static int mtk_snand_ooblayout_free(struct mtd_info *mtd, int section,
				    struct mtd_oob_region *oobfree)
{
	struct mtk_snand_mtd *msm = mtd_to_msm(mtd);

	if (section >= msm->cinfo.num_sectors)
		return -ERANGE;

	oobfree->length = msm->cinfo.fdm_size - 1;
	oobfree->offset = section * msm->cinfo.fdm_size + 1;

	return 0;
}

static irqreturn_t mtk_snand_irq(int irq, void *id)
{
	struct mtk_snand_mtd *msm = id;
	int ret;

	ret = mtk_snand_irq_process(msm->snf);
	if (ret > 0)
		return IRQ_HANDLED;

	return IRQ_NONE;
}

static int mtk_snand_enable_clk(struct mtk_snand_mtd *msm)
{
	int ret;

	ret = clk_prepare_enable(msm->nfi_clk);
	if (ret) {
		dev_err(msm->pdev.dev, "unable to enable nfi clk\n");
		return ret;
	}

	ret = clk_prepare_enable(msm->pad_clk);
	if (ret) {
		dev_err(msm->pdev.dev, "unable to enable pad clk\n");
		clk_disable_unprepare(msm->nfi_clk);
		return ret;
	}

	ret = clk_prepare_enable(msm->ecc_clk);
	if (ret) {
		dev_err(msm->pdev.dev, "unable to enable ecc clk\n");
		clk_disable_unprepare(msm->nfi_clk);
		clk_disable_unprepare(msm->pad_clk);
		return ret;
	}

	return 0;
}

static void mtk_snand_disable_clk(struct mtk_snand_mtd *msm)
{
	clk_disable_unprepare(msm->nfi_clk);
	clk_disable_unprepare(msm->pad_clk);
	clk_disable_unprepare(msm->ecc_clk);
}

static const struct mtd_ooblayout_ops mtk_snand_ooblayout = {
	.ecc = mtk_snand_ooblayout_ecc,
	.free = mtk_snand_ooblayout_free,
};

static struct mtk_snand_of_id mt7622_soc_id = { .soc = SNAND_SOC_MT7622 };
static struct mtk_snand_of_id mt7629_soc_id = { .soc = SNAND_SOC_MT7629 };

static const struct of_device_id mtk_snand_ids[] = {
	{ .compatible = "mediatek,mt7622-snand", .data = &mt7622_soc_id },
	{ .compatible = "mediatek,mt7629-snand", .data = &mt7629_soc_id },
	{ },
};

MODULE_DEVICE_TABLE(of, mtk_snand_ids);

static int mtk_snand_probe(struct platform_device *pdev)
{
	struct mtk_snand_platdata mtk_snand_pdata = {};
	struct device_node *np = pdev->dev.of_node;
	const struct of_device_id *of_soc_id;
	const struct mtk_snand_of_id *soc_id;
	struct mtk_snand_mtd *msm;
	struct mtd_info *mtd;
	struct resource *r;
	uint32_t size;
	int ret;

	of_soc_id = of_match_node(mtk_snand_ids, np);
	if (!of_soc_id)
		return -EINVAL;

	soc_id = of_soc_id->data;

	msm = devm_kzalloc(&pdev->dev, sizeof(*msm), GFP_KERNEL);
	if (!msm)
		return -ENOMEM;

	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nfi");
	msm->nfi_regs = devm_ioremap_resource(&pdev->dev, r);
	if (IS_ERR(msm->nfi_regs)) {
		ret = PTR_ERR(msm->nfi_regs);
		goto errout1;
	}

	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecc");
	msm->ecc_regs = devm_ioremap_resource(&pdev->dev, r);
	if (IS_ERR(msm->ecc_regs)) {
		ret = PTR_ERR(msm->ecc_regs);
		goto errout1;
	}

	msm->pdev.dev = &pdev->dev;
	msm->quad_spi = of_property_read_bool(np, "mediatek,quad-spi");
	msm->soc = soc_id->soc;

	msm->nfi_clk = devm_clk_get(msm->pdev.dev, "nfi_clk");
	if (IS_ERR(msm->nfi_clk)) {
		ret = PTR_ERR(msm->nfi_clk);
		dev_err(msm->pdev.dev, "unable to get nfi_clk, err = %d\n",
			ret);
		goto errout1;
	}

	msm->ecc_clk = devm_clk_get(msm->pdev.dev, "ecc_clk");
	if (IS_ERR(msm->ecc_clk)) {
		ret = PTR_ERR(msm->ecc_clk);
		dev_err(msm->pdev.dev, "unable to get ecc_clk, err = %d\n",
			ret);
		goto errout1;
	}

	msm->pad_clk = devm_clk_get(msm->pdev.dev, "pad_clk");
	if (IS_ERR(msm->pad_clk)) {
		ret = PTR_ERR(msm->pad_clk);
		dev_err(msm->pdev.dev, "unable to get pad_clk, err = %d\n",
			ret);
		goto errout1;
	}

	ret = mtk_snand_enable_clk(msm);
	if (ret)
		goto errout1;

	/* Probe SPI-NAND Flash */
	mtk_snand_pdata.soc = msm->soc;
	mtk_snand_pdata.quad_spi = msm->quad_spi;
	mtk_snand_pdata.nfi_base = msm->nfi_regs;
	mtk_snand_pdata.ecc_base = msm->ecc_regs;

	ret = mtk_snand_init(&msm->pdev, &mtk_snand_pdata, &msm->snf);
	if (ret)
		goto errout1;

	msm->irq = platform_get_irq(pdev, 0);
	if (msm->irq >= 0) {
		ret = devm_request_irq(msm->pdev.dev, msm->irq, mtk_snand_irq,
				       0x0, "mtk-snand", msm);
		if (ret) {
			dev_err(msm->pdev.dev, "failed to request snfi irq\n");
			goto errout2;
		}

		ret = dma_set_mask(msm->pdev.dev, DMA_BIT_MASK(32));
		if (ret) {
			dev_err(msm->pdev.dev, "failed to set dma mask\n");
			goto errout3;
		}
	}

	mtk_snand_get_chip_info(msm->snf, &msm->cinfo);

	size = msm->cinfo.pagesize + msm->cinfo.sparesize;
	msm->page_cache = devm_kmalloc(msm->pdev.dev, size, GFP_KERNEL);
	if (!msm->page_cache) {
		dev_err(msm->pdev.dev, "failed to allocate page cache\n");
		ret = -ENOMEM;
		goto errout3;
	}

	mutex_init(&msm->lock);

	dev_info(msm->pdev.dev,
		 "chip is %s, size %lluMB, page size %u, oob size %u\n",
		 msm->cinfo.model, msm->cinfo.chipsize >> 20,
		 msm->cinfo.pagesize, msm->cinfo.sparesize);

	/* Initialize mtd for SPI-NAND */
	mtd = &msm->mtd;

	mtd->owner = THIS_MODULE;
	mtd->dev.parent = &pdev->dev;
	mtd->type = MTD_NANDFLASH;
	mtd->flags = MTD_CAP_NANDFLASH;

	mtd_set_of_node(mtd, np);

	mtd->size = msm->cinfo.chipsize;
	mtd->erasesize = msm->cinfo.blocksize;
	mtd->writesize = msm->cinfo.pagesize;
	mtd->writebufsize = mtd->writesize;
	mtd->oobsize = msm->cinfo.sparesize;
	mtd->oobavail = msm->cinfo.num_sectors * (msm->cinfo.fdm_size - 1);

	mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
	mtd->writesize_shift = ffs(mtd->writesize) - 1;
	mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
	mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;

	mtd->ooblayout = &mtk_snand_ooblayout;

	mtd->ecc_strength = msm->cinfo.ecc_strength;
	mtd->bitflip_threshold = (mtd->ecc_strength * 3) / 4;
	mtd->ecc_step_size = msm->cinfo.sector_size;

	mtd->_erase = mtk_snand_mtd_erase;
	mtd->_read_oob = mtk_snand_mtd_read_oob;
	mtd->_write_oob = mtk_snand_mtd_write_oob;
	mtd->_block_isbad = mtk_snand_mtd_block_isbad;
	mtd->_block_markbad = mtk_snand_mtd_block_markbad;

	ret = mtd_device_register(mtd, NULL, 0);
	if (ret) {
		dev_err(msm->pdev.dev, "failed to register mtd partition\n");
		goto errout4;
	}

	platform_set_drvdata(pdev, msm);

	return 0;

errout4:
	devm_kfree(msm->pdev.dev, msm->page_cache);

errout3:
	if (msm->irq >= 0)
		devm_free_irq(msm->pdev.dev, msm->irq, msm);

errout2:
	mtk_snand_cleanup(msm->snf);

errout1:
	devm_kfree(msm->pdev.dev, msm);

	platform_set_drvdata(pdev, NULL);

	return ret;
}

static int mtk_snand_remove(struct platform_device *pdev)
{
	struct mtk_snand_mtd *msm = platform_get_drvdata(pdev);
	struct mtd_info *mtd = &msm->mtd;
	int ret;

	ret = mtd_device_unregister(mtd);
	if (ret)
		return ret;

	mtk_snand_cleanup(msm->snf);

	if (msm->irq >= 0)
		devm_free_irq(msm->pdev.dev, msm->irq, msm);

	mtk_snand_disable_clk(msm);

	devm_kfree(msm->pdev.dev, msm->page_cache);
	devm_kfree(msm->pdev.dev, msm);

	platform_set_drvdata(pdev, NULL);

	return 0;
}

static struct platform_driver mtk_snand_driver = {
	.probe = mtk_snand_probe,
	.remove = mtk_snand_remove,
	.driver = {
		.name = "mtk-snand",
		.of_match_table = mtk_snand_ids,
	},
};

module_platform_driver(mtk_snand_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Weijie Gao <weijie.gao@mediatek.com>");
MODULE_DESCRIPTION("MeidaTek SPI-NAND Flash Controller Driver");