diff options
Diffstat (limited to 'drivers/mtd/nand/raw/oxnas_nand.c')
-rw-r--r-- | drivers/mtd/nand/raw/oxnas_nand.c | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/drivers/mtd/nand/raw/oxnas_nand.c b/drivers/mtd/nand/raw/oxnas_nand.c new file mode 100644 index 000000000..f44947043 --- /dev/null +++ b/drivers/mtd/nand/raw/oxnas_nand.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Oxford Semiconductor OXNAS NAND driver + + * Copyright (C) 2016 Neil Armstrong <narmstrong@baylibre.com> + * Heavily based on plat_nand.c : + * Author: Vitaly Wool <vitalywool@gmail.com> + * Copyright (C) 2013 Ma Haijun <mahaijuns@gmail.com> + * Copyright (C) 2012 John Crispin <blogic@openwrt.org> + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/rawnand.h> +#include <linux/mtd/partitions.h> +#include <linux/of.h> + +/* Nand commands */ +#define OXNAS_NAND_CMD_ALE BIT(18) +#define OXNAS_NAND_CMD_CLE BIT(19) + +#define OXNAS_NAND_MAX_CHIPS 1 + +struct oxnas_nand_ctrl { + struct nand_controller base; + void __iomem *io_base; + struct clk *clk; + struct nand_chip *chips[OXNAS_NAND_MAX_CHIPS]; + unsigned int nchips; +}; + +static uint8_t oxnas_nand_read_byte(struct nand_chip *chip) +{ + struct oxnas_nand_ctrl *oxnas = nand_get_controller_data(chip); + + return readb(oxnas->io_base); +} + +static void oxnas_nand_read_buf(struct nand_chip *chip, u8 *buf, int len) +{ + struct oxnas_nand_ctrl *oxnas = nand_get_controller_data(chip); + + ioread8_rep(oxnas->io_base, buf, len); +} + +static void oxnas_nand_write_buf(struct nand_chip *chip, const u8 *buf, + int len) +{ + struct oxnas_nand_ctrl *oxnas = nand_get_controller_data(chip); + + iowrite8_rep(oxnas->io_base, buf, len); +} + +/* Single CS command control */ +static void oxnas_nand_cmd_ctrl(struct nand_chip *chip, int cmd, + unsigned int ctrl) +{ + struct oxnas_nand_ctrl *oxnas = nand_get_controller_data(chip); + + if (ctrl & NAND_CLE) + writeb(cmd, oxnas->io_base + OXNAS_NAND_CMD_CLE); + else if (ctrl & NAND_ALE) + writeb(cmd, oxnas->io_base + OXNAS_NAND_CMD_ALE); +} + +/* + * Probe for the NAND device. + */ +static int oxnas_nand_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *nand_np; + struct oxnas_nand_ctrl *oxnas; + struct nand_chip *chip; + struct mtd_info *mtd; + struct resource *res; + int count = 0; + int err = 0; + int i; + + /* Allocate memory for the device structure (and zero it) */ + oxnas = devm_kzalloc(&pdev->dev, sizeof(*oxnas), + GFP_KERNEL); + if (!oxnas) + return -ENOMEM; + + nand_controller_init(&oxnas->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + oxnas->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(oxnas->io_base)) + return PTR_ERR(oxnas->io_base); + + oxnas->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(oxnas->clk)) + oxnas->clk = NULL; + + /* Only a single chip node is supported */ + count = of_get_child_count(np); + if (count > 1) + return -EINVAL; + + err = clk_prepare_enable(oxnas->clk); + if (err) + return err; + + device_reset_optional(&pdev->dev); + + for_each_child_of_node(np, nand_np) { + chip = devm_kzalloc(&pdev->dev, sizeof(struct nand_chip), + GFP_KERNEL); + if (!chip) { + err = -ENOMEM; + goto err_release_child; + } + + chip->controller = &oxnas->base; + + nand_set_flash_node(chip, nand_np); + nand_set_controller_data(chip, oxnas); + + mtd = nand_to_mtd(chip); + mtd->dev.parent = &pdev->dev; + mtd->priv = chip; + + chip->legacy.cmd_ctrl = oxnas_nand_cmd_ctrl; + chip->legacy.read_buf = oxnas_nand_read_buf; + chip->legacy.read_byte = oxnas_nand_read_byte; + chip->legacy.write_buf = oxnas_nand_write_buf; + chip->legacy.chip_delay = 30; + + /* Scan to find existence of the device */ + err = nand_scan(chip, 1); + if (err) + goto err_release_child; + + err = mtd_device_register(mtd, NULL, 0); + if (err) + goto err_cleanup_nand; + + oxnas->chips[oxnas->nchips++] = chip; + } + + /* Exit if no chips found */ + if (!oxnas->nchips) { + err = -ENODEV; + goto err_clk_unprepare; + } + + platform_set_drvdata(pdev, oxnas); + + return 0; + +err_cleanup_nand: + nand_cleanup(chip); +err_release_child: + of_node_put(nand_np); + + for (i = 0; i < oxnas->nchips; i++) { + chip = oxnas->chips[i]; + WARN_ON(mtd_device_unregister(nand_to_mtd(chip))); + nand_cleanup(chip); + } + +err_clk_unprepare: + clk_disable_unprepare(oxnas->clk); + return err; +} + +static int oxnas_nand_remove(struct platform_device *pdev) +{ + struct oxnas_nand_ctrl *oxnas = platform_get_drvdata(pdev); + struct nand_chip *chip; + int i; + + for (i = 0; i < oxnas->nchips; i++) { + chip = oxnas->chips[i]; + WARN_ON(mtd_device_unregister(nand_to_mtd(chip))); + nand_cleanup(chip); + } + + clk_disable_unprepare(oxnas->clk); + + return 0; +} + +static const struct of_device_id oxnas_nand_match[] = { + { .compatible = "oxsemi,ox820-nand" }, + {}, +}; +MODULE_DEVICE_TABLE(of, oxnas_nand_match); + +static struct platform_driver oxnas_nand_driver = { + .probe = oxnas_nand_probe, + .remove = oxnas_nand_remove, + .driver = { + .name = "oxnas_nand", + .of_match_table = oxnas_nand_match, + }, +}; + +module_platform_driver(oxnas_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Oxnas NAND driver"); +MODULE_ALIAS("platform:oxnas_nand"); |