linux/drivers/mtd/nand/raw/fsl_upm.c
Uwe Kleine-König ec185b18c2 mtd: nand: Convert to platform remove callback returning void
The .remove() callback for a platform driver returns an int which makes
many driver authors wrongly assume it's possible to do error handling by
returning an error code. However the value returned is (mostly) ignored
and this typically results in resource leaks. To improve here there is a
quest to make the remove callback return void. In the first step of this
quest all drivers are converted to .remove_new() which already returns
void.

Trivially convert this driver from always returning zero in the remove
callback to the void returning variant.

Acked-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com> # atmel
Reviewed-by: Paul Cercueil <paul@crapouillou.net> # ingenic
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> # ingenic
Acked-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> # intel
Reviewed-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> # meson
Acked-by: Roger Quadros <rogerq@kernel.org> # omap_elm
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be> # renesas
Reviewed-by: Heiko Stuebner <heiko@sntech.de> # rockchip
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com> # sunxi
Acked-by: Thierry Reding <treding@nvidia.com> # tegra
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20230411113816.3472237-1-u.kleine-koenig@pengutronix.de
2023-04-11 15:42:24 +02:00

271 lines
6.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Freescale UPM NAND driver.
*
* Copyright © 2007-2008 MontaVista Software, Inc.
*
* Author: Anton Vorontsov <avorontsov@ru.mvista.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mtd/rawnand.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/mtd.h>
#include <linux/of_platform.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <asm/fsl_lbc.h>
struct fsl_upm_nand {
struct nand_controller base;
struct device *dev;
struct nand_chip chip;
struct fsl_upm upm;
uint8_t upm_addr_offset;
uint8_t upm_cmd_offset;
void __iomem *io_base;
struct gpio_desc *rnb_gpio[NAND_MAX_CHIPS];
uint32_t mchip_offsets[NAND_MAX_CHIPS];
uint32_t mchip_count;
uint32_t mchip_number;
};
static inline struct fsl_upm_nand *to_fsl_upm_nand(struct mtd_info *mtdinfo)
{
return container_of(mtd_to_nand(mtdinfo), struct fsl_upm_nand,
chip);
}
static int fun_chip_init(struct fsl_upm_nand *fun,
const struct device_node *upm_np,
const struct resource *io_res)
{
struct mtd_info *mtd = nand_to_mtd(&fun->chip);
int ret;
struct device_node *flash_np;
fun->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT;
fun->chip.ecc.algo = NAND_ECC_ALGO_HAMMING;
fun->chip.controller = &fun->base;
mtd->dev.parent = fun->dev;
flash_np = of_get_next_child(upm_np, NULL);
if (!flash_np)
return -ENODEV;
nand_set_flash_node(&fun->chip, flash_np);
mtd->name = devm_kasprintf(fun->dev, GFP_KERNEL, "0x%llx.%pOFn",
(u64)io_res->start,
flash_np);
if (!mtd->name) {
ret = -ENOMEM;
goto err;
}
ret = nand_scan(&fun->chip, fun->mchip_count);
if (ret)
goto err;
ret = mtd_device_register(mtd, NULL, 0);
err:
of_node_put(flash_np);
return ret;
}
static int func_exec_instr(struct nand_chip *chip,
const struct nand_op_instr *instr)
{
struct fsl_upm_nand *fun = to_fsl_upm_nand(nand_to_mtd(chip));
u32 mar, reg_offs = fun->mchip_offsets[fun->mchip_number];
unsigned int i;
const u8 *out;
u8 *in;
switch (instr->type) {
case NAND_OP_CMD_INSTR:
fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset);
mar = (instr->ctx.cmd.opcode << (32 - fun->upm.width)) |
reg_offs;
fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar);
fsl_upm_end_pattern(&fun->upm);
return 0;
case NAND_OP_ADDR_INSTR:
fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset);
for (i = 0; i < instr->ctx.addr.naddrs; i++) {
mar = (instr->ctx.addr.addrs[i] << (32 - fun->upm.width)) |
reg_offs;
fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar);
}
fsl_upm_end_pattern(&fun->upm);
return 0;
case NAND_OP_DATA_IN_INSTR:
in = instr->ctx.data.buf.in;
for (i = 0; i < instr->ctx.data.len; i++)
in[i] = in_8(fun->io_base + reg_offs);
return 0;
case NAND_OP_DATA_OUT_INSTR:
out = instr->ctx.data.buf.out;
for (i = 0; i < instr->ctx.data.len; i++)
out_8(fun->io_base + reg_offs, out[i]);
return 0;
case NAND_OP_WAITRDY_INSTR:
if (!fun->rnb_gpio[fun->mchip_number])
return nand_soft_waitrdy(chip, instr->ctx.waitrdy.timeout_ms);
return nand_gpio_waitrdy(chip, fun->rnb_gpio[fun->mchip_number],
instr->ctx.waitrdy.timeout_ms);
default:
return -EINVAL;
}
return 0;
}
static int fun_exec_op(struct nand_chip *chip, const struct nand_operation *op,
bool check_only)
{
struct fsl_upm_nand *fun = to_fsl_upm_nand(nand_to_mtd(chip));
unsigned int i;
int ret;
if (op->cs > NAND_MAX_CHIPS)
return -EINVAL;
if (check_only)
return 0;
fun->mchip_number = op->cs;
for (i = 0; i < op->ninstrs; i++) {
ret = func_exec_instr(chip, &op->instrs[i]);
if (ret)
return ret;
if (op->instrs[i].delay_ns)
ndelay(op->instrs[i].delay_ns);
}
return 0;
}
static const struct nand_controller_ops fun_ops = {
.exec_op = fun_exec_op,
};
static int fun_probe(struct platform_device *ofdev)
{
struct fsl_upm_nand *fun;
struct resource *io_res;
const __be32 *prop;
int ret;
int size;
int i;
fun = devm_kzalloc(&ofdev->dev, sizeof(*fun), GFP_KERNEL);
if (!fun)
return -ENOMEM;
io_res = platform_get_resource(ofdev, IORESOURCE_MEM, 0);
fun->io_base = devm_ioremap_resource(&ofdev->dev, io_res);
if (IS_ERR(fun->io_base))
return PTR_ERR(fun->io_base);
ret = fsl_upm_find(io_res->start, &fun->upm);
if (ret) {
dev_err(&ofdev->dev, "can't find UPM\n");
return ret;
}
prop = of_get_property(ofdev->dev.of_node, "fsl,upm-addr-offset",
&size);
if (!prop || size != sizeof(uint32_t)) {
dev_err(&ofdev->dev, "can't get UPM address offset\n");
return -EINVAL;
}
fun->upm_addr_offset = *prop;
prop = of_get_property(ofdev->dev.of_node, "fsl,upm-cmd-offset", &size);
if (!prop || size != sizeof(uint32_t)) {
dev_err(&ofdev->dev, "can't get UPM command offset\n");
return -EINVAL;
}
fun->upm_cmd_offset = *prop;
prop = of_get_property(ofdev->dev.of_node,
"fsl,upm-addr-line-cs-offsets", &size);
if (prop && (size / sizeof(uint32_t)) > 0) {
fun->mchip_count = size / sizeof(uint32_t);
if (fun->mchip_count >= NAND_MAX_CHIPS) {
dev_err(&ofdev->dev, "too much multiple chips\n");
return -EINVAL;
}
for (i = 0; i < fun->mchip_count; i++)
fun->mchip_offsets[i] = be32_to_cpu(prop[i]);
} else {
fun->mchip_count = 1;
}
for (i = 0; i < fun->mchip_count; i++) {
fun->rnb_gpio[i] = devm_gpiod_get_index_optional(&ofdev->dev,
NULL, i,
GPIOD_IN);
if (IS_ERR(fun->rnb_gpio[i])) {
dev_err(&ofdev->dev, "RNB gpio #%d is invalid\n", i);
return PTR_ERR(fun->rnb_gpio[i]);
}
}
nand_controller_init(&fun->base);
fun->base.ops = &fun_ops;
fun->dev = &ofdev->dev;
ret = fun_chip_init(fun, ofdev->dev.of_node, io_res);
if (ret)
return ret;
dev_set_drvdata(&ofdev->dev, fun);
return 0;
}
static void fun_remove(struct platform_device *ofdev)
{
struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev);
struct nand_chip *chip = &fun->chip;
struct mtd_info *mtd = nand_to_mtd(chip);
int ret;
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
nand_cleanup(chip);
}
static const struct of_device_id of_fun_match[] = {
{ .compatible = "fsl,upm-nand" },
{},
};
MODULE_DEVICE_TABLE(of, of_fun_match);
static struct platform_driver of_fun_driver = {
.driver = {
.name = "fsl,upm-nand",
.of_match_table = of_fun_match,
},
.probe = fun_probe,
.remove_new = fun_remove,
};
module_platform_driver(of_fun_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>");
MODULE_DESCRIPTION("Driver for NAND chips working through Freescale "
"LocalBus User-Programmable Machine");