162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic NAND driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Vitaly Wool <vitalywool@gmail.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/err.h> 962306a36Sopenharmony_ci#include <linux/io.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1462306a36Sopenharmony_ci#include <linux/mtd/platnand.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistruct plat_nand_data { 1762306a36Sopenharmony_ci struct nand_controller controller; 1862306a36Sopenharmony_ci struct nand_chip chip; 1962306a36Sopenharmony_ci void __iomem *io_base; 2062306a36Sopenharmony_ci}; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic int plat_nand_attach_chip(struct nand_chip *chip) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && 2562306a36Sopenharmony_ci chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) 2662306a36Sopenharmony_ci chip->ecc.algo = NAND_ECC_ALGO_HAMMING; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci return 0; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic const struct nand_controller_ops plat_nand_ops = { 3262306a36Sopenharmony_ci .attach_chip = plat_nand_attach_chip, 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* 3662306a36Sopenharmony_ci * Probe for the NAND device. 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_cistatic int plat_nand_probe(struct platform_device *pdev) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev); 4162306a36Sopenharmony_ci struct plat_nand_data *data; 4262306a36Sopenharmony_ci struct mtd_info *mtd; 4362306a36Sopenharmony_ci const char **part_types; 4462306a36Sopenharmony_ci int err = 0; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci if (!pdata) { 4762306a36Sopenharmony_ci dev_err(&pdev->dev, "platform_nand_data is missing\n"); 4862306a36Sopenharmony_ci return -EINVAL; 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (pdata->chip.nr_chips < 1) { 5262306a36Sopenharmony_ci dev_err(&pdev->dev, "invalid number of chips specified\n"); 5362306a36Sopenharmony_ci return -EINVAL; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci /* Allocate memory for the device structure (and zero it) */ 5762306a36Sopenharmony_ci data = devm_kzalloc(&pdev->dev, sizeof(struct plat_nand_data), 5862306a36Sopenharmony_ci GFP_KERNEL); 5962306a36Sopenharmony_ci if (!data) 6062306a36Sopenharmony_ci return -ENOMEM; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci data->controller.ops = &plat_nand_ops; 6362306a36Sopenharmony_ci nand_controller_init(&data->controller); 6462306a36Sopenharmony_ci data->chip.controller = &data->controller; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci data->io_base = devm_platform_ioremap_resource(pdev, 0); 6762306a36Sopenharmony_ci if (IS_ERR(data->io_base)) 6862306a36Sopenharmony_ci return PTR_ERR(data->io_base); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci nand_set_flash_node(&data->chip, pdev->dev.of_node); 7162306a36Sopenharmony_ci mtd = nand_to_mtd(&data->chip); 7262306a36Sopenharmony_ci mtd->dev.parent = &pdev->dev; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci data->chip.legacy.IO_ADDR_R = data->io_base; 7562306a36Sopenharmony_ci data->chip.legacy.IO_ADDR_W = data->io_base; 7662306a36Sopenharmony_ci data->chip.legacy.cmd_ctrl = pdata->ctrl.cmd_ctrl; 7762306a36Sopenharmony_ci data->chip.legacy.dev_ready = pdata->ctrl.dev_ready; 7862306a36Sopenharmony_ci data->chip.legacy.select_chip = pdata->ctrl.select_chip; 7962306a36Sopenharmony_ci data->chip.legacy.write_buf = pdata->ctrl.write_buf; 8062306a36Sopenharmony_ci data->chip.legacy.read_buf = pdata->ctrl.read_buf; 8162306a36Sopenharmony_ci data->chip.legacy.chip_delay = pdata->chip.chip_delay; 8262306a36Sopenharmony_ci data->chip.options |= pdata->chip.options; 8362306a36Sopenharmony_ci data->chip.bbt_options |= pdata->chip.bbt_options; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci platform_set_drvdata(pdev, data); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* Handle any platform specific setup */ 8862306a36Sopenharmony_ci if (pdata->ctrl.probe) { 8962306a36Sopenharmony_ci err = pdata->ctrl.probe(pdev); 9062306a36Sopenharmony_ci if (err) 9162306a36Sopenharmony_ci goto out; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* 9562306a36Sopenharmony_ci * This driver assumes that the default ECC engine should be TYPE_SOFT. 9662306a36Sopenharmony_ci * Set ->engine_type before registering the NAND devices in order to 9762306a36Sopenharmony_ci * provide a driver specific default value. 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_ci data->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* Scan to find existence of the device */ 10262306a36Sopenharmony_ci err = nand_scan(&data->chip, pdata->chip.nr_chips); 10362306a36Sopenharmony_ci if (err) 10462306a36Sopenharmony_ci goto out; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci part_types = pdata->chip.part_probe_types; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci err = mtd_device_parse_register(mtd, part_types, NULL, 10962306a36Sopenharmony_ci pdata->chip.partitions, 11062306a36Sopenharmony_ci pdata->chip.nr_partitions); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (!err) 11362306a36Sopenharmony_ci return err; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci nand_cleanup(&data->chip); 11662306a36Sopenharmony_ciout: 11762306a36Sopenharmony_ci if (pdata->ctrl.remove) 11862306a36Sopenharmony_ci pdata->ctrl.remove(pdev); 11962306a36Sopenharmony_ci return err; 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci/* 12362306a36Sopenharmony_ci * Remove a NAND device. 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_cistatic void plat_nand_remove(struct platform_device *pdev) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct plat_nand_data *data = platform_get_drvdata(pdev); 12862306a36Sopenharmony_ci struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev); 12962306a36Sopenharmony_ci struct nand_chip *chip = &data->chip; 13062306a36Sopenharmony_ci int ret; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci ret = mtd_device_unregister(nand_to_mtd(chip)); 13362306a36Sopenharmony_ci WARN_ON(ret); 13462306a36Sopenharmony_ci nand_cleanup(chip); 13562306a36Sopenharmony_ci if (pdata->ctrl.remove) 13662306a36Sopenharmony_ci pdata->ctrl.remove(pdev); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic const struct of_device_id plat_nand_match[] = { 14062306a36Sopenharmony_ci { .compatible = "gen_nand" }, 14162306a36Sopenharmony_ci {}, 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, plat_nand_match); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic struct platform_driver plat_nand_driver = { 14662306a36Sopenharmony_ci .probe = plat_nand_probe, 14762306a36Sopenharmony_ci .remove_new = plat_nand_remove, 14862306a36Sopenharmony_ci .driver = { 14962306a36Sopenharmony_ci .name = "gen_nand", 15062306a36Sopenharmony_ci .of_match_table = plat_nand_match, 15162306a36Sopenharmony_ci }, 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cimodule_platform_driver(plat_nand_driver); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 15762306a36Sopenharmony_ciMODULE_AUTHOR("Vitaly Wool"); 15862306a36Sopenharmony_ciMODULE_DESCRIPTION("Simple generic NAND driver"); 15962306a36Sopenharmony_ciMODULE_ALIAS("platform:gen_nand"); 160