Keep a reference to the shared FCM controller for each scanned chip,
including chips retained after unbind. Count active banks separately so
a new binding cannot reuse controller state still owned by old MTD
handles.
Unregister and drain the chip before unmapping its chip-select window.
Release NAND metadata, chip storage and the shared controller reference
only after the final MTD put.
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
drivers/mtd/nand/raw/fsl_elbc_nand.c | 68 ++++++++++++++++++++++++------------
1 file changed, 45 insertions(+), 23 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c
index c655d27bc4cc..c27e89050270 100644
--- a/drivers/mtd/nand/raw/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_elbc_nand.c
@@ -48,6 +48,7 @@ struct fsl_elbc_mtd {
struct fsl_elbc_fcm_ctrl {
struct nand_controller controller;
+ struct kref refcnt;
struct fsl_elbc_mtd *chips[MAX_BANKS];
u8 __iomem *addr; /* Address of assigned FCM buffer */
@@ -837,23 +838,35 @@ static const struct nand_controller_ops fsl_elbc_controller_ops = {
.attach_chip = fsl_elbc_attach_chip,
};
-static int fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv)
+static void fsl_elbc_controller_release(struct kref *ref)
{
- struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
- struct mtd_info *mtd = nand_to_mtd(&priv->chip);
+ kfree(container_of(ref, struct fsl_elbc_fcm_ctrl, refcnt));
+}
+
+static void fsl_elbc_chip_release(struct nand_chip *chip)
+{
+ struct fsl_elbc_fcm_ctrl *ctrl;
- kfree(mtd->name);
+ ctrl = container_of(chip->controller, struct fsl_elbc_fcm_ctrl, controller);
+ kfree(nand_get_controller_data(chip));
+ kref_put(&ctrl->refcnt, fsl_elbc_controller_release);
+}
+
+static DEFINE_MUTEX(fsl_elbc_nand_mutex);
+
+static void fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv)
+{
+ struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
if (priv->vbase)
iounmap(priv->vbase);
+ guard(mutex)(&fsl_elbc_nand_mutex);
elbc_fcm_ctrl->chips[priv->bank] = NULL;
- kfree(priv);
- return 0;
+ if (!--elbc_fcm_ctrl->counter)
+ priv->ctrl->nand = NULL;
}
-static DEFINE_MUTEX(fsl_elbc_nand_mutex);
-
static int fsl_elbc_nand_probe(struct platform_device *pdev)
{
struct fsl_lbc_regs __iomem *lbc;
@@ -867,6 +880,7 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
struct device *dev;
struct device_node *node = pdev->dev.of_node;
struct mtd_info *mtd;
+ const char *name;
if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
return dev_err_probe(&pdev->dev, -EPROBE_DEFER, "lbc_ctrl_dev missing\n");
@@ -904,21 +918,25 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
elbc_fcm_ctrl = kzalloc_obj(*elbc_fcm_ctrl);
if (!elbc_fcm_ctrl) {
mutex_unlock(&fsl_elbc_nand_mutex);
- ret = -ENOMEM;
- goto err;
+ kfree(priv);
+ return -ENOMEM;
}
- elbc_fcm_ctrl->counter++;
+ kref_init(&elbc_fcm_ctrl->refcnt);
nand_controller_init(&elbc_fcm_ctrl->controller);
fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl;
} else {
elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
+ kref_get(&elbc_fcm_ctrl->refcnt);
}
+ elbc_fcm_ctrl->counter++;
mutex_unlock(&fsl_elbc_nand_mutex);
elbc_fcm_ctrl->chips[bank] = priv;
priv->bank = bank;
priv->ctrl = fsl_lbc_ctrl_dev;
+ priv->chip.controller = &elbc_fcm_ctrl->controller;
+ nand_set_controller_data(&priv->chip, priv);
priv->dev = &pdev->dev;
dev_set_drvdata(priv->dev, priv);
@@ -945,11 +963,21 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
if (ret)
goto err;
+ name = mtd->name;
+ ret = nand_set_release(&priv->chip, fsl_elbc_chip_release);
+ if (ret)
+ goto cleanup_nand;
+ kfree(name);
+
/* First look for RedBoot table or partitions on the command
* line, these take precedence over device tree information */
ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
- if (ret)
- goto cleanup_nand;
+ if (ret) {
+ nand_disconnect(&priv->chip);
+ fsl_elbc_chip_remove(priv);
+ mtd_put_master(mtd);
+ return ret;
+ }
pr_info("eLBC NAND device at 0x%llx, bank %d\n",
(unsigned long long)res.start, priv->bank);
@@ -959,32 +987,26 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
cleanup_nand:
nand_cleanup(&priv->chip);
err:
+ kfree(nand_to_mtd(&priv->chip)->name);
fsl_elbc_chip_remove(priv);
+ fsl_elbc_chip_release(&priv->chip);
return ret;
}
static void fsl_elbc_nand_remove(struct platform_device *pdev)
{
- struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
struct fsl_elbc_mtd *priv = dev_get_drvdata(&pdev->dev);
struct nand_chip *chip = &priv->chip;
int ret;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
fsl_elbc_chip_remove(priv);
- mutex_lock(&fsl_elbc_nand_mutex);
- elbc_fcm_ctrl->counter--;
- if (!elbc_fcm_ctrl->counter) {
- fsl_lbc_ctrl_dev->nand = NULL;
- kfree(elbc_fcm_ctrl);
- }
- mutex_unlock(&fsl_elbc_nand_mutex);
-
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id fsl_elbc_nand_match[] = {
--
2.53.0