162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2006-2007 PA Semi, Inc 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Egor Martovetsky <egor@pasemi.com> 662306a36Sopenharmony_ci * Maintained by: Olof Johansson <olof@lixom.net> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Driver for the PWRficient onchip NAND flash interface 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#undef DEBUG 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1662306a36Sopenharmony_ci#include <linux/mtd/rawnand.h> 1762306a36Sopenharmony_ci#include <linux/of_address.h> 1862306a36Sopenharmony_ci#include <linux/of_irq.h> 1962306a36Sopenharmony_ci#include <linux/of_platform.h> 2062306a36Sopenharmony_ci#include <linux/platform_device.h> 2162306a36Sopenharmony_ci#include <linux/pci.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <asm/io.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define LBICTRL_LPCCTL_NR 0x00004000 2662306a36Sopenharmony_ci#define CLE_PIN_CTL 15 2762306a36Sopenharmony_ci#define ALE_PIN_CTL 14 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct pasemi_ddata { 3062306a36Sopenharmony_ci struct nand_chip chip; 3162306a36Sopenharmony_ci unsigned int lpcctl; 3262306a36Sopenharmony_ci struct nand_controller controller; 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic const char driver_name[] = "pasemi-nand"; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic void pasemi_read_buf(struct nand_chip *chip, u_char *buf, int len) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci while (len > 0x800) { 4062306a36Sopenharmony_ci memcpy_fromio(buf, chip->legacy.IO_ADDR_R, 0x800); 4162306a36Sopenharmony_ci buf += 0x800; 4262306a36Sopenharmony_ci len -= 0x800; 4362306a36Sopenharmony_ci } 4462306a36Sopenharmony_ci memcpy_fromio(buf, chip->legacy.IO_ADDR_R, len); 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic void pasemi_write_buf(struct nand_chip *chip, const u_char *buf, 4862306a36Sopenharmony_ci int len) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci while (len > 0x800) { 5162306a36Sopenharmony_ci memcpy_toio(chip->legacy.IO_ADDR_R, buf, 0x800); 5262306a36Sopenharmony_ci buf += 0x800; 5362306a36Sopenharmony_ci len -= 0x800; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci memcpy_toio(chip->legacy.IO_ADDR_R, buf, len); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic void pasemi_hwcontrol(struct nand_chip *chip, int cmd, 5962306a36Sopenharmony_ci unsigned int ctrl) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct pasemi_ddata *ddata = container_of(chip, struct pasemi_ddata, chip); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (cmd == NAND_CMD_NONE) 6462306a36Sopenharmony_ci return; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci if (ctrl & NAND_CLE) 6762306a36Sopenharmony_ci out_8(chip->legacy.IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); 6862306a36Sopenharmony_ci else 6962306a36Sopenharmony_ci out_8(chip->legacy.IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* Push out posted writes */ 7262306a36Sopenharmony_ci eieio(); 7362306a36Sopenharmony_ci inl(ddata->lpcctl); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic int pasemi_device_ready(struct nand_chip *chip) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct pasemi_ddata *ddata = container_of(chip, struct pasemi_ddata, chip); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci return !!(inl(ddata->lpcctl) & LBICTRL_LPCCTL_NR); 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int pasemi_attach_chip(struct nand_chip *chip) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && 8662306a36Sopenharmony_ci chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) 8762306a36Sopenharmony_ci chip->ecc.algo = NAND_ECC_ALGO_HAMMING; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return 0; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic const struct nand_controller_ops pasemi_ops = { 9362306a36Sopenharmony_ci .attach_chip = pasemi_attach_chip, 9462306a36Sopenharmony_ci}; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic int pasemi_nand_probe(struct platform_device *ofdev) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct device *dev = &ofdev->dev; 9962306a36Sopenharmony_ci struct pci_dev *pdev; 10062306a36Sopenharmony_ci struct device_node *np = dev->of_node; 10162306a36Sopenharmony_ci struct resource res; 10262306a36Sopenharmony_ci struct nand_chip *chip; 10362306a36Sopenharmony_ci struct nand_controller *controller; 10462306a36Sopenharmony_ci int err = 0; 10562306a36Sopenharmony_ci struct pasemi_ddata *ddata; 10662306a36Sopenharmony_ci struct mtd_info *pasemi_nand_mtd; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci err = of_address_to_resource(np, 0, &res); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (err) 11162306a36Sopenharmony_ci return -EINVAL; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci dev_dbg(dev, "pasemi_nand at %pR\n", &res); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* Allocate memory for MTD device structure and private data */ 11662306a36Sopenharmony_ci ddata = kzalloc(sizeof(*ddata), GFP_KERNEL); 11762306a36Sopenharmony_ci if (!ddata) { 11862306a36Sopenharmony_ci err = -ENOMEM; 11962306a36Sopenharmony_ci goto out; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci platform_set_drvdata(ofdev, ddata); 12262306a36Sopenharmony_ci chip = &ddata->chip; 12362306a36Sopenharmony_ci controller = &ddata->controller; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci controller->ops = &pasemi_ops; 12662306a36Sopenharmony_ci nand_controller_init(controller); 12762306a36Sopenharmony_ci chip->controller = controller; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci pasemi_nand_mtd = nand_to_mtd(chip); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* Link the private data with the MTD structure */ 13262306a36Sopenharmony_ci pasemi_nand_mtd->dev.parent = dev; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci chip->legacy.IO_ADDR_R = of_iomap(np, 0); 13562306a36Sopenharmony_ci chip->legacy.IO_ADDR_W = chip->legacy.IO_ADDR_R; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (!chip->legacy.IO_ADDR_R) { 13862306a36Sopenharmony_ci err = -EIO; 13962306a36Sopenharmony_ci goto out_mtd; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL); 14362306a36Sopenharmony_ci if (!pdev) { 14462306a36Sopenharmony_ci err = -ENODEV; 14562306a36Sopenharmony_ci goto out_ior; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci ddata->lpcctl = pci_resource_start(pdev, 0); 14962306a36Sopenharmony_ci pci_dev_put(pdev); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (!request_region(ddata->lpcctl, 4, driver_name)) { 15262306a36Sopenharmony_ci err = -EBUSY; 15362306a36Sopenharmony_ci goto out_ior; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci chip->legacy.cmd_ctrl = pasemi_hwcontrol; 15762306a36Sopenharmony_ci chip->legacy.dev_ready = pasemi_device_ready; 15862306a36Sopenharmony_ci chip->legacy.read_buf = pasemi_read_buf; 15962306a36Sopenharmony_ci chip->legacy.write_buf = pasemi_write_buf; 16062306a36Sopenharmony_ci chip->legacy.chip_delay = 0; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Enable the following for a flash based bad block table */ 16362306a36Sopenharmony_ci chip->bbt_options = NAND_BBT_USE_FLASH; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* 16662306a36Sopenharmony_ci * This driver assumes that the default ECC engine should be TYPE_SOFT. 16762306a36Sopenharmony_ci * Set ->engine_type before registering the NAND devices in order to 16862306a36Sopenharmony_ci * provide a driver specific default value. 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_ci chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* Scan to find existence of the device */ 17362306a36Sopenharmony_ci err = nand_scan(chip, 1); 17462306a36Sopenharmony_ci if (err) 17562306a36Sopenharmony_ci goto out_lpc; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) { 17862306a36Sopenharmony_ci dev_err(dev, "Unable to register MTD device\n"); 17962306a36Sopenharmony_ci err = -ENODEV; 18062306a36Sopenharmony_ci goto out_cleanup_nand; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci dev_info(dev, "PA Semi NAND flash at %pR, control at I/O %x\n", &res, 18462306a36Sopenharmony_ci ddata->lpcctl); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return 0; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci out_cleanup_nand: 18962306a36Sopenharmony_ci nand_cleanup(chip); 19062306a36Sopenharmony_ci out_lpc: 19162306a36Sopenharmony_ci release_region(ddata->lpcctl, 4); 19262306a36Sopenharmony_ci out_ior: 19362306a36Sopenharmony_ci iounmap(chip->legacy.IO_ADDR_R); 19462306a36Sopenharmony_ci out_mtd: 19562306a36Sopenharmony_ci kfree(ddata); 19662306a36Sopenharmony_ci out: 19762306a36Sopenharmony_ci return err; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void pasemi_nand_remove(struct platform_device *ofdev) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct pasemi_ddata *ddata = platform_get_drvdata(ofdev); 20362306a36Sopenharmony_ci struct mtd_info *pasemi_nand_mtd; 20462306a36Sopenharmony_ci int ret; 20562306a36Sopenharmony_ci struct nand_chip *chip; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci chip = &ddata->chip; 20862306a36Sopenharmony_ci pasemi_nand_mtd = nand_to_mtd(chip); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* Release resources, unregister device */ 21162306a36Sopenharmony_ci ret = mtd_device_unregister(pasemi_nand_mtd); 21262306a36Sopenharmony_ci WARN_ON(ret); 21362306a36Sopenharmony_ci nand_cleanup(chip); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci release_region(ddata->lpcctl, 4); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci iounmap(chip->legacy.IO_ADDR_R); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* Free the MTD device structure */ 22062306a36Sopenharmony_ci kfree(ddata); 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic const struct of_device_id pasemi_nand_match[] = 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci { 22662306a36Sopenharmony_ci .compatible = "pasemi,localbus-nand", 22762306a36Sopenharmony_ci }, 22862306a36Sopenharmony_ci {}, 22962306a36Sopenharmony_ci}; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, pasemi_nand_match); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic struct platform_driver pasemi_nand_driver = 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci .driver = { 23662306a36Sopenharmony_ci .name = driver_name, 23762306a36Sopenharmony_ci .of_match_table = pasemi_nand_match, 23862306a36Sopenharmony_ci }, 23962306a36Sopenharmony_ci .probe = pasemi_nand_probe, 24062306a36Sopenharmony_ci .remove_new = pasemi_nand_remove, 24162306a36Sopenharmony_ci}; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cimodule_platform_driver(pasemi_nand_driver); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 24662306a36Sopenharmony_ciMODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); 24762306a36Sopenharmony_ciMODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient"); 248