162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Overview: 462306a36Sopenharmony_ci * Platform independent driver for NDFC (NanD Flash Controller) 562306a36Sopenharmony_ci * integrated into EP440 cores 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Ported to an OF platform driver by Sean MacLennan 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * The NDFC supports multiple chips, but this driver only supports a 1062306a36Sopenharmony_ci * single chip since I do not have access to any boards with 1162306a36Sopenharmony_ci * multiple chips. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * Author: Thomas Gleixner 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Copyright 2006 IBM 1662306a36Sopenharmony_ci * Copyright 2008 PIKA Technologies 1762306a36Sopenharmony_ci * Sean MacLennan <smaclennan@pikatech.com> 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci#include <linux/module.h> 2062306a36Sopenharmony_ci#include <linux/mtd/rawnand.h> 2162306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 2262306a36Sopenharmony_ci#include <linux/mtd/ndfc.h> 2362306a36Sopenharmony_ci#include <linux/slab.h> 2462306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 2562306a36Sopenharmony_ci#include <linux/of.h> 2662306a36Sopenharmony_ci#include <linux/of_address.h> 2762306a36Sopenharmony_ci#include <linux/platform_device.h> 2862306a36Sopenharmony_ci#include <asm/io.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define NDFC_MAX_CS 4 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistruct ndfc_controller { 3362306a36Sopenharmony_ci struct platform_device *ofdev; 3462306a36Sopenharmony_ci void __iomem *ndfcbase; 3562306a36Sopenharmony_ci struct nand_chip chip; 3662306a36Sopenharmony_ci int chip_select; 3762306a36Sopenharmony_ci struct nand_controller ndfc_control; 3862306a36Sopenharmony_ci}; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS]; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic void ndfc_select_chip(struct nand_chip *nchip, int chip) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci uint32_t ccr; 4562306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(nchip); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); 4862306a36Sopenharmony_ci if (chip >= 0) { 4962306a36Sopenharmony_ci ccr &= ~NDFC_CCR_BS_MASK; 5062306a36Sopenharmony_ci ccr |= NDFC_CCR_BS(chip + ndfc->chip_select); 5162306a36Sopenharmony_ci } else 5262306a36Sopenharmony_ci ccr |= NDFC_CCR_RESET_CE; 5362306a36Sopenharmony_ci out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic void ndfc_hwcontrol(struct nand_chip *chip, int cmd, unsigned int ctrl) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(chip); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci if (cmd == NAND_CMD_NONE) 6162306a36Sopenharmony_ci return; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (ctrl & NAND_CLE) 6462306a36Sopenharmony_ci writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD); 6562306a36Sopenharmony_ci else 6662306a36Sopenharmony_ci writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE); 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic int ndfc_ready(struct nand_chip *chip) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(chip); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic void ndfc_enable_hwecc(struct nand_chip *chip, int mode) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci uint32_t ccr; 7962306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(chip); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); 8262306a36Sopenharmony_ci ccr |= NDFC_CCR_RESET_ECC; 8362306a36Sopenharmony_ci out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); 8462306a36Sopenharmony_ci wmb(); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic int ndfc_calculate_ecc(struct nand_chip *chip, 8862306a36Sopenharmony_ci const u_char *dat, u_char *ecc_code) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(chip); 9162306a36Sopenharmony_ci uint32_t ecc; 9262306a36Sopenharmony_ci uint8_t *p = (uint8_t *)&ecc; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci wmb(); 9562306a36Sopenharmony_ci ecc = in_be32(ndfc->ndfcbase + NDFC_ECC); 9662306a36Sopenharmony_ci /* The NDFC uses Smart Media (SMC) bytes order */ 9762306a36Sopenharmony_ci ecc_code[0] = p[1]; 9862306a36Sopenharmony_ci ecc_code[1] = p[2]; 9962306a36Sopenharmony_ci ecc_code[2] = p[3]; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci return 0; 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* 10562306a36Sopenharmony_ci * Speedups for buffer read/write/verify 10662306a36Sopenharmony_ci * 10762306a36Sopenharmony_ci * NDFC allows 32bit read/write of data. So we can speed up the buffer 10862306a36Sopenharmony_ci * functions. No further checking, as nand_base will always read/write 10962306a36Sopenharmony_ci * page aligned. 11062306a36Sopenharmony_ci */ 11162306a36Sopenharmony_cistatic void ndfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(chip); 11462306a36Sopenharmony_ci uint32_t *p = (uint32_t *) buf; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci for(;len > 0; len -= 4) 11762306a36Sopenharmony_ci *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA); 11862306a36Sopenharmony_ci} 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct ndfc_controller *ndfc = nand_get_controller_data(chip); 12362306a36Sopenharmony_ci uint32_t *p = (uint32_t *) buf; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci for(;len > 0; len -= 4) 12662306a36Sopenharmony_ci out_be32(ndfc->ndfcbase + NDFC_DATA, *p++); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* 13062306a36Sopenharmony_ci * Initialize chip structure 13162306a36Sopenharmony_ci */ 13262306a36Sopenharmony_cistatic int ndfc_chip_init(struct ndfc_controller *ndfc, 13362306a36Sopenharmony_ci struct device_node *node) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct device_node *flash_np; 13662306a36Sopenharmony_ci struct nand_chip *chip = &ndfc->chip; 13762306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 13862306a36Sopenharmony_ci int ret; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci chip->legacy.IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; 14162306a36Sopenharmony_ci chip->legacy.IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; 14262306a36Sopenharmony_ci chip->legacy.cmd_ctrl = ndfc_hwcontrol; 14362306a36Sopenharmony_ci chip->legacy.dev_ready = ndfc_ready; 14462306a36Sopenharmony_ci chip->legacy.select_chip = ndfc_select_chip; 14562306a36Sopenharmony_ci chip->legacy.chip_delay = 50; 14662306a36Sopenharmony_ci chip->controller = &ndfc->ndfc_control; 14762306a36Sopenharmony_ci chip->legacy.read_buf = ndfc_read_buf; 14862306a36Sopenharmony_ci chip->legacy.write_buf = ndfc_write_buf; 14962306a36Sopenharmony_ci chip->ecc.correct = rawnand_sw_hamming_correct; 15062306a36Sopenharmony_ci chip->ecc.hwctl = ndfc_enable_hwecc; 15162306a36Sopenharmony_ci chip->ecc.calculate = ndfc_calculate_ecc; 15262306a36Sopenharmony_ci chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; 15362306a36Sopenharmony_ci chip->ecc.size = 256; 15462306a36Sopenharmony_ci chip->ecc.bytes = 3; 15562306a36Sopenharmony_ci chip->ecc.strength = 1; 15662306a36Sopenharmony_ci nand_set_controller_data(chip, ndfc); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci mtd->dev.parent = &ndfc->ofdev->dev; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci flash_np = of_get_next_child(node, NULL); 16162306a36Sopenharmony_ci if (!flash_np) 16262306a36Sopenharmony_ci return -ENODEV; 16362306a36Sopenharmony_ci nand_set_flash_node(chip, flash_np); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci mtd->name = kasprintf(GFP_KERNEL, "%s.%pOFn", dev_name(&ndfc->ofdev->dev), 16662306a36Sopenharmony_ci flash_np); 16762306a36Sopenharmony_ci if (!mtd->name) { 16862306a36Sopenharmony_ci ret = -ENOMEM; 16962306a36Sopenharmony_ci goto err; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci ret = nand_scan(chip, 1); 17362306a36Sopenharmony_ci if (ret) 17462306a36Sopenharmony_ci goto err; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci ret = mtd_device_register(mtd, NULL, 0); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cierr: 17962306a36Sopenharmony_ci of_node_put(flash_np); 18062306a36Sopenharmony_ci if (ret) 18162306a36Sopenharmony_ci kfree(mtd->name); 18262306a36Sopenharmony_ci return ret; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic int ndfc_probe(struct platform_device *ofdev) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct ndfc_controller *ndfc; 18862306a36Sopenharmony_ci const __be32 *reg; 18962306a36Sopenharmony_ci u32 ccr; 19062306a36Sopenharmony_ci u32 cs; 19162306a36Sopenharmony_ci int err, len; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* Read the reg property to get the chip select */ 19462306a36Sopenharmony_ci reg = of_get_property(ofdev->dev.of_node, "reg", &len); 19562306a36Sopenharmony_ci if (reg == NULL || len != 12) { 19662306a36Sopenharmony_ci dev_err(&ofdev->dev, "unable read reg property (%d)\n", len); 19762306a36Sopenharmony_ci return -ENOENT; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci cs = be32_to_cpu(reg[0]); 20162306a36Sopenharmony_ci if (cs >= NDFC_MAX_CS) { 20262306a36Sopenharmony_ci dev_err(&ofdev->dev, "invalid CS number (%d)\n", cs); 20362306a36Sopenharmony_ci return -EINVAL; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci ndfc = &ndfc_ctrl[cs]; 20762306a36Sopenharmony_ci ndfc->chip_select = cs; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci nand_controller_init(&ndfc->ndfc_control); 21062306a36Sopenharmony_ci ndfc->ofdev = ofdev; 21162306a36Sopenharmony_ci dev_set_drvdata(&ofdev->dev, ndfc); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0); 21462306a36Sopenharmony_ci if (!ndfc->ndfcbase) { 21562306a36Sopenharmony_ci dev_err(&ofdev->dev, "failed to get memory\n"); 21662306a36Sopenharmony_ci return -EIO; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci ccr = NDFC_CCR_BS(ndfc->chip_select); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* It is ok if ccr does not exist - just default to 0 */ 22262306a36Sopenharmony_ci reg = of_get_property(ofdev->dev.of_node, "ccr", NULL); 22362306a36Sopenharmony_ci if (reg) 22462306a36Sopenharmony_ci ccr |= be32_to_cpup(reg); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci /* Set the bank settings if given */ 22962306a36Sopenharmony_ci reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL); 23062306a36Sopenharmony_ci if (reg) { 23162306a36Sopenharmony_ci int offset = NDFC_BCFG0 + (ndfc->chip_select << 2); 23262306a36Sopenharmony_ci out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg)); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci err = ndfc_chip_init(ndfc, ofdev->dev.of_node); 23662306a36Sopenharmony_ci if (err) { 23762306a36Sopenharmony_ci iounmap(ndfc->ndfcbase); 23862306a36Sopenharmony_ci return err; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic void ndfc_remove(struct platform_device *ofdev) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev); 24762306a36Sopenharmony_ci struct nand_chip *chip = &ndfc->chip; 24862306a36Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 24962306a36Sopenharmony_ci int ret; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci ret = mtd_device_unregister(mtd); 25262306a36Sopenharmony_ci WARN_ON(ret); 25362306a36Sopenharmony_ci nand_cleanup(chip); 25462306a36Sopenharmony_ci kfree(mtd->name); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic const struct of_device_id ndfc_match[] = { 25862306a36Sopenharmony_ci { .compatible = "ibm,ndfc", }, 25962306a36Sopenharmony_ci {} 26062306a36Sopenharmony_ci}; 26162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ndfc_match); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic struct platform_driver ndfc_driver = { 26462306a36Sopenharmony_ci .driver = { 26562306a36Sopenharmony_ci .name = "ndfc", 26662306a36Sopenharmony_ci .of_match_table = ndfc_match, 26762306a36Sopenharmony_ci }, 26862306a36Sopenharmony_ci .probe = ndfc_probe, 26962306a36Sopenharmony_ci .remove_new = ndfc_remove, 27062306a36Sopenharmony_ci}; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cimodule_platform_driver(ndfc_driver); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 27562306a36Sopenharmony_ciMODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); 27662306a36Sopenharmony_ciMODULE_DESCRIPTION("OF Platform driver for NDFC"); 277