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