18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2006-2007 PA Semi, Inc 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Author: Egor Martovetsky <egor@pasemi.com> 68c2ecf20Sopenharmony_ci * Maintained by: Olof Johansson <olof@lixom.net> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Driver for the PWRficient onchip NAND flash interface 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#undef DEBUG 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 168c2ecf20Sopenharmony_ci#include <linux/mtd/rawnand.h> 178c2ecf20Sopenharmony_ci#include <linux/mtd/nand_ecc.h> 188c2ecf20Sopenharmony_ci#include <linux/of_address.h> 198c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 208c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 228c2ecf20Sopenharmony_ci#include <linux/pci.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#include <asm/io.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci#define LBICTRL_LPCCTL_NR 0x00004000 278c2ecf20Sopenharmony_ci#define CLE_PIN_CTL 15 288c2ecf20Sopenharmony_ci#define ALE_PIN_CTL 14 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic unsigned int lpcctl; 318c2ecf20Sopenharmony_cistatic struct mtd_info *pasemi_nand_mtd; 328c2ecf20Sopenharmony_cistatic struct nand_controller controller; 338c2ecf20Sopenharmony_cistatic const char driver_name[] = "pasemi-nand"; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic void pasemi_read_buf(struct nand_chip *chip, u_char *buf, int len) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci while (len > 0x800) { 388c2ecf20Sopenharmony_ci memcpy_fromio(buf, chip->legacy.IO_ADDR_R, 0x800); 398c2ecf20Sopenharmony_ci buf += 0x800; 408c2ecf20Sopenharmony_ci len -= 0x800; 418c2ecf20Sopenharmony_ci } 428c2ecf20Sopenharmony_ci memcpy_fromio(buf, chip->legacy.IO_ADDR_R, len); 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic void pasemi_write_buf(struct nand_chip *chip, const u_char *buf, 468c2ecf20Sopenharmony_ci int len) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci while (len > 0x800) { 498c2ecf20Sopenharmony_ci memcpy_toio(chip->legacy.IO_ADDR_R, buf, 0x800); 508c2ecf20Sopenharmony_ci buf += 0x800; 518c2ecf20Sopenharmony_ci len -= 0x800; 528c2ecf20Sopenharmony_ci } 538c2ecf20Sopenharmony_ci memcpy_toio(chip->legacy.IO_ADDR_R, buf, len); 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic void pasemi_hwcontrol(struct nand_chip *chip, int cmd, 578c2ecf20Sopenharmony_ci unsigned int ctrl) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci if (cmd == NAND_CMD_NONE) 608c2ecf20Sopenharmony_ci return; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (ctrl & NAND_CLE) 638c2ecf20Sopenharmony_ci out_8(chip->legacy.IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); 648c2ecf20Sopenharmony_ci else 658c2ecf20Sopenharmony_ci out_8(chip->legacy.IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* Push out posted writes */ 688c2ecf20Sopenharmony_ci eieio(); 698c2ecf20Sopenharmony_ci inl(lpcctl); 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic int pasemi_device_ready(struct nand_chip *chip) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci return !!(inl(lpcctl) & LBICTRL_LPCCTL_NR); 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic int pasemi_attach_chip(struct nand_chip *chip) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && 808c2ecf20Sopenharmony_ci chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) 818c2ecf20Sopenharmony_ci chip->ecc.algo = NAND_ECC_ALGO_HAMMING; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return 0; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic const struct nand_controller_ops pasemi_ops = { 878c2ecf20Sopenharmony_ci .attach_chip = pasemi_attach_chip, 888c2ecf20Sopenharmony_ci}; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cistatic int pasemi_nand_probe(struct platform_device *ofdev) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct device *dev = &ofdev->dev; 938c2ecf20Sopenharmony_ci struct pci_dev *pdev; 948c2ecf20Sopenharmony_ci struct device_node *np = dev->of_node; 958c2ecf20Sopenharmony_ci struct resource res; 968c2ecf20Sopenharmony_ci struct nand_chip *chip; 978c2ecf20Sopenharmony_ci int err = 0; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci err = of_address_to_resource(np, 0, &res); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci if (err) 1028c2ecf20Sopenharmony_ci return -EINVAL; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* We only support one device at the moment */ 1058c2ecf20Sopenharmony_ci if (pasemi_nand_mtd) 1068c2ecf20Sopenharmony_ci return -ENODEV; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci dev_dbg(dev, "pasemi_nand at %pR\n", &res); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* Allocate memory for MTD device structure and private data */ 1118c2ecf20Sopenharmony_ci chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 1128c2ecf20Sopenharmony_ci if (!chip) { 1138c2ecf20Sopenharmony_ci err = -ENOMEM; 1148c2ecf20Sopenharmony_ci goto out; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci controller.ops = &pasemi_ops; 1188c2ecf20Sopenharmony_ci nand_controller_init(&controller); 1198c2ecf20Sopenharmony_ci chip->controller = &controller; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci pasemi_nand_mtd = nand_to_mtd(chip); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* Link the private data with the MTD structure */ 1248c2ecf20Sopenharmony_ci pasemi_nand_mtd->dev.parent = dev; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci chip->legacy.IO_ADDR_R = of_iomap(np, 0); 1278c2ecf20Sopenharmony_ci chip->legacy.IO_ADDR_W = chip->legacy.IO_ADDR_R; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if (!chip->legacy.IO_ADDR_R) { 1308c2ecf20Sopenharmony_ci err = -EIO; 1318c2ecf20Sopenharmony_ci goto out_mtd; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL); 1358c2ecf20Sopenharmony_ci if (!pdev) { 1368c2ecf20Sopenharmony_ci err = -ENODEV; 1378c2ecf20Sopenharmony_ci goto out_ior; 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci lpcctl = pci_resource_start(pdev, 0); 1418c2ecf20Sopenharmony_ci pci_dev_put(pdev); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (!request_region(lpcctl, 4, driver_name)) { 1448c2ecf20Sopenharmony_ci err = -EBUSY; 1458c2ecf20Sopenharmony_ci goto out_ior; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci chip->legacy.cmd_ctrl = pasemi_hwcontrol; 1498c2ecf20Sopenharmony_ci chip->legacy.dev_ready = pasemi_device_ready; 1508c2ecf20Sopenharmony_ci chip->legacy.read_buf = pasemi_read_buf; 1518c2ecf20Sopenharmony_ci chip->legacy.write_buf = pasemi_write_buf; 1528c2ecf20Sopenharmony_ci chip->legacy.chip_delay = 0; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci /* Enable the following for a flash based bad block table */ 1558c2ecf20Sopenharmony_ci chip->bbt_options = NAND_BBT_USE_FLASH; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci /* 1588c2ecf20Sopenharmony_ci * This driver assumes that the default ECC engine should be TYPE_SOFT. 1598c2ecf20Sopenharmony_ci * Set ->engine_type before registering the NAND devices in order to 1608c2ecf20Sopenharmony_ci * provide a driver specific default value. 1618c2ecf20Sopenharmony_ci */ 1628c2ecf20Sopenharmony_ci chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* Scan to find existence of the device */ 1658c2ecf20Sopenharmony_ci err = nand_scan(chip, 1); 1668c2ecf20Sopenharmony_ci if (err) 1678c2ecf20Sopenharmony_ci goto out_lpc; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) { 1708c2ecf20Sopenharmony_ci dev_err(dev, "Unable to register MTD device\n"); 1718c2ecf20Sopenharmony_ci err = -ENODEV; 1728c2ecf20Sopenharmony_ci goto out_cleanup_nand; 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci dev_info(dev, "PA Semi NAND flash at %pR, control at I/O %x\n", &res, 1768c2ecf20Sopenharmony_ci lpcctl); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci return 0; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci out_cleanup_nand: 1818c2ecf20Sopenharmony_ci nand_cleanup(chip); 1828c2ecf20Sopenharmony_ci out_lpc: 1838c2ecf20Sopenharmony_ci release_region(lpcctl, 4); 1848c2ecf20Sopenharmony_ci out_ior: 1858c2ecf20Sopenharmony_ci iounmap(chip->legacy.IO_ADDR_R); 1868c2ecf20Sopenharmony_ci out_mtd: 1878c2ecf20Sopenharmony_ci kfree(chip); 1888c2ecf20Sopenharmony_ci out: 1898c2ecf20Sopenharmony_ci return err; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic int pasemi_nand_remove(struct platform_device *ofdev) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct nand_chip *chip; 1958c2ecf20Sopenharmony_ci int ret; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (!pasemi_nand_mtd) 1988c2ecf20Sopenharmony_ci return 0; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci chip = mtd_to_nand(pasemi_nand_mtd); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* Release resources, unregister device */ 2038c2ecf20Sopenharmony_ci ret = mtd_device_unregister(pasemi_nand_mtd); 2048c2ecf20Sopenharmony_ci WARN_ON(ret); 2058c2ecf20Sopenharmony_ci nand_cleanup(chip); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci release_region(lpcctl, 4); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci iounmap(chip->legacy.IO_ADDR_R); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* Free the MTD device structure */ 2128c2ecf20Sopenharmony_ci kfree(chip); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci pasemi_nand_mtd = NULL; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci return 0; 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic const struct of_device_id pasemi_nand_match[] = 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci { 2228c2ecf20Sopenharmony_ci .compatible = "pasemi,localbus-nand", 2238c2ecf20Sopenharmony_ci }, 2248c2ecf20Sopenharmony_ci {}, 2258c2ecf20Sopenharmony_ci}; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pasemi_nand_match); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic struct platform_driver pasemi_nand_driver = 2308c2ecf20Sopenharmony_ci{ 2318c2ecf20Sopenharmony_ci .driver = { 2328c2ecf20Sopenharmony_ci .name = driver_name, 2338c2ecf20Sopenharmony_ci .of_match_table = pasemi_nand_match, 2348c2ecf20Sopenharmony_ci }, 2358c2ecf20Sopenharmony_ci .probe = pasemi_nand_probe, 2368c2ecf20Sopenharmony_ci .remove = pasemi_nand_remove, 2378c2ecf20Sopenharmony_ci}; 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cimodule_platform_driver(pasemi_nand_driver); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2428c2ecf20Sopenharmony_ciMODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); 2438c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient"); 244