Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | /* * Copyright © 2009 Nuvoton technology corporation. * * Wan ZongShun <mcuos.com@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation;version 2 of the License. * */ #include <linux/slab.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/mtd/mtd.h> #include <linux/mtd/rawnand.h> #include <linux/mtd/partitions.h> #define REG_FMICSR 0x00 #define REG_SMCSR 0xa0 #define REG_SMISR 0xac #define REG_SMCMD 0xb0 #define REG_SMADDR 0xb4 #define REG_SMDATA 0xb8 #define RESET_FMI 0x01 #define NAND_EN 0x08 #define READYBUSY (0x01 << 18) #define SWRST 0x01 #define PSIZE (0x01 << 3) #define DMARWEN (0x03 << 1) #define BUSWID (0x01 << 4) #define ECC4EN (0x01 << 5) #define WP (0x01 << 24) #define NANDCS (0x01 << 25) #define ENDADDR (0x01 << 31) #define read_data_reg(dev) \ __raw_readl((dev)->reg + REG_SMDATA) #define write_data_reg(dev, val) \ __raw_writel((val), (dev)->reg + REG_SMDATA) #define write_cmd_reg(dev, val) \ __raw_writel((val), (dev)->reg + REG_SMCMD) #define write_addr_reg(dev, val) \ __raw_writel((val), (dev)->reg + REG_SMADDR) struct nuc900_nand { struct nand_chip chip; void __iomem *reg; struct clk *clk; spinlock_t lock; }; static inline struct nuc900_nand *mtd_to_nuc900(struct mtd_info *mtd) { return container_of(mtd_to_nand(mtd), struct nuc900_nand, chip); } static const struct mtd_partition partitions[] = { { .name = "NAND FS 0", .offset = 0, .size = 8 * 1024 * 1024 }, { .name = "NAND FS 1", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL } }; static unsigned char nuc900_nand_read_byte(struct mtd_info *mtd) { unsigned char ret; struct nuc900_nand *nand = mtd_to_nuc900(mtd); ret = (unsigned char)read_data_reg(nand); return ret; } static void nuc900_nand_read_buf(struct mtd_info *mtd, unsigned char *buf, int len) { int i; struct nuc900_nand *nand = mtd_to_nuc900(mtd); for (i = 0; i < len; i++) buf[i] = (unsigned char)read_data_reg(nand); } static void nuc900_nand_write_buf(struct mtd_info *mtd, const unsigned char *buf, int len) { int i; struct nuc900_nand *nand = mtd_to_nuc900(mtd); for (i = 0; i < len; i++) write_data_reg(nand, buf[i]); } static int nuc900_check_rb(struct nuc900_nand *nand) { unsigned int val; spin_lock(&nand->lock); val = __raw_readl(nand->reg + REG_SMISR); val &= READYBUSY; spin_unlock(&nand->lock); return val; } static int nuc900_nand_devready(struct mtd_info *mtd) { struct nuc900_nand *nand = mtd_to_nuc900(mtd); int ready; ready = (nuc900_check_rb(nand)) ? 1 : 0; return ready; } static void nuc900_nand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr) { register struct nand_chip *chip = mtd_to_nand(mtd); struct nuc900_nand *nand = mtd_to_nuc900(mtd); if (command == NAND_CMD_READOOB) { column += mtd->writesize; command = NAND_CMD_READ0; } write_cmd_reg(nand, command & 0xff); if (column != -1 || page_addr != -1) { if (column != -1) { if (chip->options & NAND_BUSWIDTH_16 && !nand_opcode_8bits(command)) column >>= 1; write_addr_reg(nand, column); write_addr_reg(nand, column >> 8 | ENDADDR); } if (page_addr != -1) { write_addr_reg(nand, page_addr); if (chip->options & NAND_ROW_ADDR_3) { write_addr_reg(nand, page_addr >> 8); write_addr_reg(nand, page_addr >> 16 | ENDADDR); } else { write_addr_reg(nand, page_addr >> 8 | ENDADDR); } } } switch (command) { case NAND_CMD_CACHEDPROG: case NAND_CMD_PAGEPROG: case NAND_CMD_ERASE1: case NAND_CMD_ERASE2: case NAND_CMD_SEQIN: case NAND_CMD_RNDIN: case NAND_CMD_STATUS: return; case NAND_CMD_RESET: if (chip->dev_ready) break; udelay(chip->chip_delay); write_cmd_reg(nand, NAND_CMD_STATUS); write_cmd_reg(nand, command); while (!nuc900_check_rb(nand)) ; return; case NAND_CMD_RNDOUT: write_cmd_reg(nand, NAND_CMD_RNDOUTSTART); return; case NAND_CMD_READ0: write_cmd_reg(nand, NAND_CMD_READSTART); default: if (!chip->dev_ready) { udelay(chip->chip_delay); return; } } /* Apply this short delay always to ensure that we do wait tWB in * any case on any machine. */ ndelay(100); while (!chip->dev_ready(mtd)) ; } static void nuc900_nand_enable(struct nuc900_nand *nand) { unsigned int val; spin_lock(&nand->lock); __raw_writel(RESET_FMI, (nand->reg + REG_FMICSR)); val = __raw_readl(nand->reg + REG_FMICSR); if (!(val & NAND_EN)) __raw_writel(val | NAND_EN, nand->reg + REG_FMICSR); val = __raw_readl(nand->reg + REG_SMCSR); val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS); val |= WP; __raw_writel(val, nand->reg + REG_SMCSR); spin_unlock(&nand->lock); } static int nuc900_nand_probe(struct platform_device *pdev) { struct nuc900_nand *nuc900_nand; struct nand_chip *chip; struct mtd_info *mtd; struct resource *res; nuc900_nand = devm_kzalloc(&pdev->dev, sizeof(struct nuc900_nand), GFP_KERNEL); if (!nuc900_nand) return -ENOMEM; chip = &(nuc900_nand->chip); mtd = nand_to_mtd(chip); mtd->dev.parent = &pdev->dev; spin_lock_init(&nuc900_nand->lock); nuc900_nand->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(nuc900_nand->clk)) return -ENOENT; clk_enable(nuc900_nand->clk); chip->cmdfunc = nuc900_nand_command_lp; chip->dev_ready = nuc900_nand_devready; chip->read_byte = nuc900_nand_read_byte; chip->write_buf = nuc900_nand_write_buf; chip->read_buf = nuc900_nand_read_buf; chip->chip_delay = 50; chip->options = 0; chip->ecc.mode = NAND_ECC_SOFT; chip->ecc.algo = NAND_ECC_HAMMING; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); nuc900_nand->reg = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(nuc900_nand->reg)) return PTR_ERR(nuc900_nand->reg); nuc900_nand_enable(nuc900_nand); if (nand_scan(mtd, 1)) return -ENXIO; mtd_device_register(mtd, partitions, ARRAY_SIZE(partitions)); platform_set_drvdata(pdev, nuc900_nand); return 0; } static int nuc900_nand_remove(struct platform_device *pdev) { struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev); nand_release(nand_to_mtd(&nuc900_nand->chip)); clk_disable(nuc900_nand->clk); return 0; } static struct platform_driver nuc900_nand_driver = { .probe = nuc900_nand_probe, .remove = nuc900_nand_remove, .driver = { .name = "nuc900-fmi", }, }; module_platform_driver(nuc900_nand_driver); MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:nuc900-fmi"); |