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