1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2006-2007 PA Semi, Inc 4 * 5 * Author: Egor Martovetsky <egor@pasemi.com> 6 * Maintained by: Olof Johansson <olof@lixom.net> 7 * 8 * Driver for the PWRficient onchip NAND flash interface 9 */ 10 11 #undef DEBUG 12 13 #include <linux/slab.h> 14 #include <linux/module.h> 15 #include <linux/mtd/mtd.h> 16 #include <linux/mtd/rawnand.h> 17 #include <linux/mtd/nand_ecc.h> 18 #include <linux/of_address.h> 19 #include <linux/of_irq.h> 20 #include <linux/of_platform.h> 21 #include <linux/platform_device.h> 22 #include <linux/pci.h> 23 24 #include <asm/io.h> 25 26 #define LBICTRL_LPCCTL_NR 0x00004000 27 #define CLE_PIN_CTL 15 28 #define ALE_PIN_CTL 14 29 30 static unsigned int lpcctl; 31 static struct mtd_info *pasemi_nand_mtd; 32 static struct nand_controller controller; 33 static const char driver_name[] = "pasemi-nand"; 34 35 static void pasemi_read_buf(struct nand_chip *chip, u_char *buf, int len) 36 { 37 while (len > 0x800) { 38 memcpy_fromio(buf, chip->legacy.IO_ADDR_R, 0x800); 39 buf += 0x800; 40 len -= 0x800; 41 } 42 memcpy_fromio(buf, chip->legacy.IO_ADDR_R, len); 43 } 44 45 static void pasemi_write_buf(struct nand_chip *chip, const u_char *buf, 46 int len) 47 { 48 while (len > 0x800) { 49 memcpy_toio(chip->legacy.IO_ADDR_R, buf, 0x800); 50 buf += 0x800; 51 len -= 0x800; 52 } 53 memcpy_toio(chip->legacy.IO_ADDR_R, buf, len); 54 } 55 56 static void pasemi_hwcontrol(struct nand_chip *chip, int cmd, 57 unsigned int ctrl) 58 { 59 if (cmd == NAND_CMD_NONE) 60 return; 61 62 if (ctrl & NAND_CLE) 63 out_8(chip->legacy.IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); 64 else 65 out_8(chip->legacy.IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); 66 67 /* Push out posted writes */ 68 eieio(); 69 inl(lpcctl); 70 } 71 72 static int pasemi_device_ready(struct nand_chip *chip) 73 { 74 return !!(inl(lpcctl) & LBICTRL_LPCCTL_NR); 75 } 76 77 static int pasemi_attach_chip(struct nand_chip *chip) 78 { 79 chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; 80 81 if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) 82 chip->ecc.algo = NAND_ECC_ALGO_HAMMING; 83 84 return 0; 85 } 86 87 static const struct nand_controller_ops pasemi_ops = { 88 .attach_chip = pasemi_attach_chip, 89 }; 90 91 static int pasemi_nand_probe(struct platform_device *ofdev) 92 { 93 struct device *dev = &ofdev->dev; 94 struct pci_dev *pdev; 95 struct device_node *np = dev->of_node; 96 struct resource res; 97 struct nand_chip *chip; 98 int err = 0; 99 100 err = of_address_to_resource(np, 0, &res); 101 102 if (err) 103 return -EINVAL; 104 105 /* We only support one device at the moment */ 106 if (pasemi_nand_mtd) 107 return -ENODEV; 108 109 dev_dbg(dev, "pasemi_nand at %pR\n", &res); 110 111 /* Allocate memory for MTD device structure and private data */ 112 chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 113 if (!chip) { 114 err = -ENOMEM; 115 goto out; 116 } 117 118 controller.ops = &pasemi_ops; 119 nand_controller_init(&controller); 120 chip->controller = &controller; 121 122 pasemi_nand_mtd = nand_to_mtd(chip); 123 124 /* Link the private data with the MTD structure */ 125 pasemi_nand_mtd->dev.parent = dev; 126 127 chip->legacy.IO_ADDR_R = of_iomap(np, 0); 128 chip->legacy.IO_ADDR_W = chip->legacy.IO_ADDR_R; 129 130 if (!chip->legacy.IO_ADDR_R) { 131 err = -EIO; 132 goto out_mtd; 133 } 134 135 pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL); 136 if (!pdev) { 137 err = -ENODEV; 138 goto out_ior; 139 } 140 141 lpcctl = pci_resource_start(pdev, 0); 142 pci_dev_put(pdev); 143 144 if (!request_region(lpcctl, 4, driver_name)) { 145 err = -EBUSY; 146 goto out_ior; 147 } 148 149 chip->legacy.cmd_ctrl = pasemi_hwcontrol; 150 chip->legacy.dev_ready = pasemi_device_ready; 151 chip->legacy.read_buf = pasemi_read_buf; 152 chip->legacy.write_buf = pasemi_write_buf; 153 chip->legacy.chip_delay = 0; 154 155 /* Enable the following for a flash based bad block table */ 156 chip->bbt_options = NAND_BBT_USE_FLASH; 157 158 /* Scan to find existence of the device */ 159 err = nand_scan(chip, 1); 160 if (err) 161 goto out_lpc; 162 163 if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) { 164 dev_err(dev, "Unable to register MTD device\n"); 165 err = -ENODEV; 166 goto out_cleanup_nand; 167 } 168 169 dev_info(dev, "PA Semi NAND flash at %pR, control at I/O %x\n", &res, 170 lpcctl); 171 172 return 0; 173 174 out_cleanup_nand: 175 nand_cleanup(chip); 176 out_lpc: 177 release_region(lpcctl, 4); 178 out_ior: 179 iounmap(chip->legacy.IO_ADDR_R); 180 out_mtd: 181 kfree(chip); 182 out: 183 return err; 184 } 185 186 static int pasemi_nand_remove(struct platform_device *ofdev) 187 { 188 struct nand_chip *chip; 189 int ret; 190 191 if (!pasemi_nand_mtd) 192 return 0; 193 194 chip = mtd_to_nand(pasemi_nand_mtd); 195 196 /* Release resources, unregister device */ 197 ret = mtd_device_unregister(pasemi_nand_mtd); 198 WARN_ON(ret); 199 nand_cleanup(chip); 200 201 release_region(lpcctl, 4); 202 203 iounmap(chip->legacy.IO_ADDR_R); 204 205 /* Free the MTD device structure */ 206 kfree(chip); 207 208 pasemi_nand_mtd = NULL; 209 210 return 0; 211 } 212 213 static const struct of_device_id pasemi_nand_match[] = 214 { 215 { 216 .compatible = "pasemi,localbus-nand", 217 }, 218 {}, 219 }; 220 221 MODULE_DEVICE_TABLE(of, pasemi_nand_match); 222 223 static struct platform_driver pasemi_nand_driver = 224 { 225 .driver = { 226 .name = driver_name, 227 .of_match_table = pasemi_nand_match, 228 }, 229 .probe = pasemi_nand_probe, 230 .remove = pasemi_nand_remove, 231 }; 232 233 module_platform_driver(pasemi_nand_driver); 234 235 MODULE_LICENSE("GPL"); 236 MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>"); 237 MODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient"); 238