| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * (C) 2005, 2006 Red Hat Inc. |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Author: David Woodhouse <dwmw2@infradead.org> |
|---|
| 5 | 6 | * Tom Sylla <tom.sylla@amd.com> |
|---|
| 6 | 7 | * |
|---|
| 7 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 8 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 9 | | - * published by the Free Software Foundation. |
|---|
| 10 | | - * |
|---|
| 11 | 8 | * Overview: |
|---|
| 12 | 9 | * This is a device driver for the NAND flash controller found on |
|---|
| 13 | 10 | * the AMD CS5535/CS5536 companion chipsets for the Geode processor. |
|---|
| 14 | 11 | * mtd-id for command line partitioning is cs553x_nand_cs[0-3] |
|---|
| 15 | 12 | * where 0-3 reflects the chip select for NAND. |
|---|
| 16 | | - * |
|---|
| 17 | 13 | */ |
|---|
| 18 | 14 | |
|---|
| 19 | 15 | #include <linux/kernel.h> |
|---|
| .. | .. |
|---|
| 25 | 21 | #include <linux/mtd/rawnand.h> |
|---|
| 26 | 22 | #include <linux/mtd/nand_ecc.h> |
|---|
| 27 | 23 | #include <linux/mtd/partitions.h> |
|---|
| 24 | +#include <linux/iopoll.h> |
|---|
| 28 | 25 | |
|---|
| 29 | 26 | #include <asm/msr.h> |
|---|
| 30 | | -#include <asm/io.h> |
|---|
| 31 | 27 | |
|---|
| 32 | 28 | #define NR_CS553X_CONTROLLERS 4 |
|---|
| 33 | 29 | |
|---|
| .. | .. |
|---|
| 93 | 89 | #define CS_NAND_ECC_CLRECC (1<<1) |
|---|
| 94 | 90 | #define CS_NAND_ECC_ENECC (1<<0) |
|---|
| 95 | 91 | |
|---|
| 96 | | -static void cs553x_read_buf(struct mtd_info *mtd, u_char *buf, int len) |
|---|
| 97 | | -{ |
|---|
| 98 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 92 | +struct cs553x_nand_controller { |
|---|
| 93 | + struct nand_controller base; |
|---|
| 94 | + struct nand_chip chip; |
|---|
| 95 | + void __iomem *mmio; |
|---|
| 96 | +}; |
|---|
| 99 | 97 | |
|---|
| 98 | +static struct cs553x_nand_controller * |
|---|
| 99 | +to_cs553x(struct nand_controller *controller) |
|---|
| 100 | +{ |
|---|
| 101 | + return container_of(controller, struct cs553x_nand_controller, base); |
|---|
| 102 | +} |
|---|
| 103 | + |
|---|
| 104 | +static int cs553x_write_ctrl_byte(struct cs553x_nand_controller *cs553x, |
|---|
| 105 | + u32 ctl, u8 data) |
|---|
| 106 | +{ |
|---|
| 107 | + u8 status; |
|---|
| 108 | + int ret; |
|---|
| 109 | + |
|---|
| 110 | + writeb(ctl, cs553x->mmio + MM_NAND_CTL); |
|---|
| 111 | + writeb(data, cs553x->mmio + MM_NAND_IO); |
|---|
| 112 | + ret = readb_poll_timeout_atomic(cs553x->mmio + MM_NAND_STS, status, |
|---|
| 113 | + !(status & CS_NAND_CTLR_BUSY), 1, |
|---|
| 114 | + 100000); |
|---|
| 115 | + if (ret) |
|---|
| 116 | + return ret; |
|---|
| 117 | + |
|---|
| 118 | + return 0; |
|---|
| 119 | +} |
|---|
| 120 | + |
|---|
| 121 | +static void cs553x_data_in(struct cs553x_nand_controller *cs553x, void *buf, |
|---|
| 122 | + unsigned int len) |
|---|
| 123 | +{ |
|---|
| 124 | + writeb(0, cs553x->mmio + MM_NAND_CTL); |
|---|
| 100 | 125 | while (unlikely(len > 0x800)) { |
|---|
| 101 | | - memcpy_fromio(buf, this->IO_ADDR_R, 0x800); |
|---|
| 126 | + memcpy_fromio(buf, cs553x->mmio, 0x800); |
|---|
| 102 | 127 | buf += 0x800; |
|---|
| 103 | 128 | len -= 0x800; |
|---|
| 104 | 129 | } |
|---|
| 105 | | - memcpy_fromio(buf, this->IO_ADDR_R, len); |
|---|
| 130 | + memcpy_fromio(buf, cs553x->mmio, len); |
|---|
| 106 | 131 | } |
|---|
| 107 | 132 | |
|---|
| 108 | | -static void cs553x_write_buf(struct mtd_info *mtd, const u_char *buf, int len) |
|---|
| 133 | +static void cs553x_data_out(struct cs553x_nand_controller *cs553x, |
|---|
| 134 | + const void *buf, unsigned int len) |
|---|
| 109 | 135 | { |
|---|
| 110 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 111 | | - |
|---|
| 136 | + writeb(0, cs553x->mmio + MM_NAND_CTL); |
|---|
| 112 | 137 | while (unlikely(len > 0x800)) { |
|---|
| 113 | | - memcpy_toio(this->IO_ADDR_R, buf, 0x800); |
|---|
| 138 | + memcpy_toio(cs553x->mmio, buf, 0x800); |
|---|
| 114 | 139 | buf += 0x800; |
|---|
| 115 | 140 | len -= 0x800; |
|---|
| 116 | 141 | } |
|---|
| 117 | | - memcpy_toio(this->IO_ADDR_R, buf, len); |
|---|
| 142 | + memcpy_toio(cs553x->mmio, buf, len); |
|---|
| 118 | 143 | } |
|---|
| 119 | 144 | |
|---|
| 120 | | -static unsigned char cs553x_read_byte(struct mtd_info *mtd) |
|---|
| 145 | +static int cs553x_wait_ready(struct cs553x_nand_controller *cs553x, |
|---|
| 146 | + unsigned int timeout_ms) |
|---|
| 121 | 147 | { |
|---|
| 122 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 123 | | - return readb(this->IO_ADDR_R); |
|---|
| 148 | + u8 mask = CS_NAND_CTLR_BUSY | CS_NAND_STS_FLASH_RDY; |
|---|
| 149 | + u8 status; |
|---|
| 150 | + |
|---|
| 151 | + return readb_poll_timeout(cs553x->mmio + MM_NAND_STS, status, |
|---|
| 152 | + (status & mask) == CS_NAND_STS_FLASH_RDY, 100, |
|---|
| 153 | + timeout_ms * 1000); |
|---|
| 124 | 154 | } |
|---|
| 125 | 155 | |
|---|
| 126 | | -static void cs553x_write_byte(struct mtd_info *mtd, u_char byte) |
|---|
| 156 | +static int cs553x_exec_instr(struct cs553x_nand_controller *cs553x, |
|---|
| 157 | + const struct nand_op_instr *instr) |
|---|
| 127 | 158 | { |
|---|
| 128 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 129 | | - int i = 100000; |
|---|
| 159 | + unsigned int i; |
|---|
| 160 | + int ret = 0; |
|---|
| 130 | 161 | |
|---|
| 131 | | - while (i && readb(this->IO_ADDR_R + MM_NAND_STS) & CS_NAND_CTLR_BUSY) { |
|---|
| 132 | | - udelay(1); |
|---|
| 133 | | - i--; |
|---|
| 162 | + switch (instr->type) { |
|---|
| 163 | + case NAND_OP_CMD_INSTR: |
|---|
| 164 | + ret = cs553x_write_ctrl_byte(cs553x, CS_NAND_CTL_CLE, |
|---|
| 165 | + instr->ctx.cmd.opcode); |
|---|
| 166 | + break; |
|---|
| 167 | + |
|---|
| 168 | + case NAND_OP_ADDR_INSTR: |
|---|
| 169 | + for (i = 0; i < instr->ctx.addr.naddrs; i++) { |
|---|
| 170 | + ret = cs553x_write_ctrl_byte(cs553x, CS_NAND_CTL_ALE, |
|---|
| 171 | + instr->ctx.addr.addrs[i]); |
|---|
| 172 | + if (ret) |
|---|
| 173 | + break; |
|---|
| 174 | + } |
|---|
| 175 | + break; |
|---|
| 176 | + |
|---|
| 177 | + case NAND_OP_DATA_IN_INSTR: |
|---|
| 178 | + cs553x_data_in(cs553x, instr->ctx.data.buf.in, |
|---|
| 179 | + instr->ctx.data.len); |
|---|
| 180 | + break; |
|---|
| 181 | + |
|---|
| 182 | + case NAND_OP_DATA_OUT_INSTR: |
|---|
| 183 | + cs553x_data_out(cs553x, instr->ctx.data.buf.out, |
|---|
| 184 | + instr->ctx.data.len); |
|---|
| 185 | + break; |
|---|
| 186 | + |
|---|
| 187 | + case NAND_OP_WAITRDY_INSTR: |
|---|
| 188 | + ret = cs553x_wait_ready(cs553x, instr->ctx.waitrdy.timeout_ms); |
|---|
| 189 | + break; |
|---|
| 134 | 190 | } |
|---|
| 135 | | - writeb(byte, this->IO_ADDR_W + 0x801); |
|---|
| 191 | + |
|---|
| 192 | + if (instr->delay_ns) |
|---|
| 193 | + ndelay(instr->delay_ns); |
|---|
| 194 | + |
|---|
| 195 | + return ret; |
|---|
| 136 | 196 | } |
|---|
| 137 | 197 | |
|---|
| 138 | | -static void cs553x_hwcontrol(struct mtd_info *mtd, int cmd, |
|---|
| 139 | | - unsigned int ctrl) |
|---|
| 198 | +static int cs553x_exec_op(struct nand_chip *this, |
|---|
| 199 | + const struct nand_operation *op, |
|---|
| 200 | + bool check_only) |
|---|
| 140 | 201 | { |
|---|
| 141 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 142 | | - void __iomem *mmio_base = this->IO_ADDR_R; |
|---|
| 143 | | - if (ctrl & NAND_CTRL_CHANGE) { |
|---|
| 144 | | - unsigned char ctl = (ctrl & ~NAND_CTRL_CHANGE ) ^ 0x01; |
|---|
| 145 | | - writeb(ctl, mmio_base + MM_NAND_CTL); |
|---|
| 202 | + struct cs553x_nand_controller *cs553x = to_cs553x(this->controller); |
|---|
| 203 | + unsigned int i; |
|---|
| 204 | + int ret; |
|---|
| 205 | + |
|---|
| 206 | + if (check_only) |
|---|
| 207 | + return true; |
|---|
| 208 | + |
|---|
| 209 | + /* De-assert the CE pin */ |
|---|
| 210 | + writeb(0, cs553x->mmio + MM_NAND_CTL); |
|---|
| 211 | + for (i = 0; i < op->ninstrs; i++) { |
|---|
| 212 | + ret = cs553x_exec_instr(cs553x, &op->instrs[i]); |
|---|
| 213 | + if (ret) |
|---|
| 214 | + break; |
|---|
| 146 | 215 | } |
|---|
| 147 | | - if (cmd != NAND_CMD_NONE) |
|---|
| 148 | | - cs553x_write_byte(mtd, cmd); |
|---|
| 216 | + |
|---|
| 217 | + /* Re-assert the CE pin. */ |
|---|
| 218 | + writeb(CS_NAND_CTL_CE, cs553x->mmio + MM_NAND_CTL); |
|---|
| 219 | + |
|---|
| 220 | + return ret; |
|---|
| 149 | 221 | } |
|---|
| 150 | 222 | |
|---|
| 151 | | -static int cs553x_device_ready(struct mtd_info *mtd) |
|---|
| 223 | +static void cs_enable_hwecc(struct nand_chip *this, int mode) |
|---|
| 152 | 224 | { |
|---|
| 153 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 154 | | - void __iomem *mmio_base = this->IO_ADDR_R; |
|---|
| 155 | | - unsigned char foo = readb(mmio_base + MM_NAND_STS); |
|---|
| 225 | + struct cs553x_nand_controller *cs553x = to_cs553x(this->controller); |
|---|
| 156 | 226 | |
|---|
| 157 | | - return (foo & CS_NAND_STS_FLASH_RDY) && !(foo & CS_NAND_CTLR_BUSY); |
|---|
| 227 | + writeb(0x07, cs553x->mmio + MM_NAND_ECC_CTL); |
|---|
| 158 | 228 | } |
|---|
| 159 | 229 | |
|---|
| 160 | | -static void cs_enable_hwecc(struct mtd_info *mtd, int mode) |
|---|
| 230 | +static int cs_calculate_ecc(struct nand_chip *this, const u_char *dat, |
|---|
| 231 | + u_char *ecc_code) |
|---|
| 161 | 232 | { |
|---|
| 162 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 163 | | - void __iomem *mmio_base = this->IO_ADDR_R; |
|---|
| 164 | | - |
|---|
| 165 | | - writeb(0x07, mmio_base + MM_NAND_ECC_CTL); |
|---|
| 166 | | -} |
|---|
| 167 | | - |
|---|
| 168 | | -static int cs_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) |
|---|
| 169 | | -{ |
|---|
| 233 | + struct cs553x_nand_controller *cs553x = to_cs553x(this->controller); |
|---|
| 170 | 234 | uint32_t ecc; |
|---|
| 171 | | - struct nand_chip *this = mtd_to_nand(mtd); |
|---|
| 172 | | - void __iomem *mmio_base = this->IO_ADDR_R; |
|---|
| 173 | 235 | |
|---|
| 174 | | - ecc = readl(mmio_base + MM_NAND_STS); |
|---|
| 236 | + ecc = readl(cs553x->mmio + MM_NAND_STS); |
|---|
| 175 | 237 | |
|---|
| 176 | 238 | ecc_code[1] = ecc >> 8; |
|---|
| 177 | 239 | ecc_code[0] = ecc >> 16; |
|---|
| .. | .. |
|---|
| 179 | 241 | return 0; |
|---|
| 180 | 242 | } |
|---|
| 181 | 243 | |
|---|
| 182 | | -static struct mtd_info *cs553x_mtd[4]; |
|---|
| 244 | +static struct cs553x_nand_controller *controllers[4]; |
|---|
| 245 | + |
|---|
| 246 | +static int cs553x_attach_chip(struct nand_chip *chip) |
|---|
| 247 | +{ |
|---|
| 248 | + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) |
|---|
| 249 | + return 0; |
|---|
| 250 | + |
|---|
| 251 | + chip->ecc.size = 256; |
|---|
| 252 | + chip->ecc.bytes = 3; |
|---|
| 253 | + chip->ecc.hwctl = cs_enable_hwecc; |
|---|
| 254 | + chip->ecc.calculate = cs_calculate_ecc; |
|---|
| 255 | + chip->ecc.correct = nand_correct_data; |
|---|
| 256 | + chip->ecc.strength = 1; |
|---|
| 257 | + |
|---|
| 258 | + return 0; |
|---|
| 259 | +} |
|---|
| 260 | + |
|---|
| 261 | +static const struct nand_controller_ops cs553x_nand_controller_ops = { |
|---|
| 262 | + .exec_op = cs553x_exec_op, |
|---|
| 263 | + .attach_chip = cs553x_attach_chip, |
|---|
| 264 | +}; |
|---|
| 183 | 265 | |
|---|
| 184 | 266 | static int __init cs553x_init_one(int cs, int mmio, unsigned long adr) |
|---|
| 185 | 267 | { |
|---|
| 268 | + struct cs553x_nand_controller *controller; |
|---|
| 186 | 269 | int err = 0; |
|---|
| 187 | 270 | struct nand_chip *this; |
|---|
| 188 | 271 | struct mtd_info *new_mtd; |
|---|
| .. | .. |
|---|
| 196 | 279 | } |
|---|
| 197 | 280 | |
|---|
| 198 | 281 | /* Allocate memory for MTD device structure and private data */ |
|---|
| 199 | | - this = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); |
|---|
| 200 | | - if (!this) { |
|---|
| 282 | + controller = kzalloc(sizeof(*controller), GFP_KERNEL); |
|---|
| 283 | + if (!controller) { |
|---|
| 201 | 284 | err = -ENOMEM; |
|---|
| 202 | 285 | goto out; |
|---|
| 203 | 286 | } |
|---|
| 204 | 287 | |
|---|
| 288 | + this = &controller->chip; |
|---|
| 289 | + nand_controller_init(&controller->base); |
|---|
| 290 | + controller->base.ops = &cs553x_nand_controller_ops; |
|---|
| 291 | + this->controller = &controller->base; |
|---|
| 205 | 292 | new_mtd = nand_to_mtd(this); |
|---|
| 206 | 293 | |
|---|
| 207 | 294 | /* Link the private data with the MTD structure */ |
|---|
| 208 | 295 | new_mtd->owner = THIS_MODULE; |
|---|
| 209 | 296 | |
|---|
| 210 | 297 | /* map physical address */ |
|---|
| 211 | | - this->IO_ADDR_R = this->IO_ADDR_W = ioremap(adr, 4096); |
|---|
| 212 | | - if (!this->IO_ADDR_R) { |
|---|
| 298 | + controller->mmio = ioremap(adr, 4096); |
|---|
| 299 | + if (!controller->mmio) { |
|---|
| 213 | 300 | pr_warn("ioremap cs553x NAND @0x%08lx failed\n", adr); |
|---|
| 214 | 301 | err = -EIO; |
|---|
| 215 | 302 | goto out_mtd; |
|---|
| 216 | 303 | } |
|---|
| 217 | | - |
|---|
| 218 | | - this->cmd_ctrl = cs553x_hwcontrol; |
|---|
| 219 | | - this->dev_ready = cs553x_device_ready; |
|---|
| 220 | | - this->read_byte = cs553x_read_byte; |
|---|
| 221 | | - this->read_buf = cs553x_read_buf; |
|---|
| 222 | | - this->write_buf = cs553x_write_buf; |
|---|
| 223 | | - |
|---|
| 224 | | - this->chip_delay = 0; |
|---|
| 225 | | - |
|---|
| 226 | | - this->ecc.mode = NAND_ECC_HW; |
|---|
| 227 | | - this->ecc.size = 256; |
|---|
| 228 | | - this->ecc.bytes = 3; |
|---|
| 229 | | - this->ecc.hwctl = cs_enable_hwecc; |
|---|
| 230 | | - this->ecc.calculate = cs_calculate_ecc; |
|---|
| 231 | | - this->ecc.correct = nand_correct_data; |
|---|
| 232 | | - this->ecc.strength = 1; |
|---|
| 233 | 304 | |
|---|
| 234 | 305 | /* Enable the following for a flash based bad block table */ |
|---|
| 235 | 306 | this->bbt_options = NAND_BBT_USE_FLASH; |
|---|
| .. | .. |
|---|
| 245 | 316 | if (err) |
|---|
| 246 | 317 | goto out_free; |
|---|
| 247 | 318 | |
|---|
| 248 | | - cs553x_mtd[cs] = new_mtd; |
|---|
| 319 | + controllers[cs] = controller; |
|---|
| 249 | 320 | goto out; |
|---|
| 250 | 321 | |
|---|
| 251 | 322 | out_free: |
|---|
| 252 | 323 | kfree(new_mtd->name); |
|---|
| 253 | 324 | out_ior: |
|---|
| 254 | | - iounmap(this->IO_ADDR_R); |
|---|
| 325 | + iounmap(controller->mmio); |
|---|
| 255 | 326 | out_mtd: |
|---|
| 256 | | - kfree(this); |
|---|
| 327 | + kfree(controller); |
|---|
| 257 | 328 | out: |
|---|
| 258 | 329 | return err; |
|---|
| 259 | 330 | } |
|---|
| .. | .. |
|---|
| 308 | 379 | /* Register all devices together here. This means we can easily hack it to |
|---|
| 309 | 380 | do mtdconcat etc. if we want to. */ |
|---|
| 310 | 381 | for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { |
|---|
| 311 | | - if (cs553x_mtd[i]) { |
|---|
| 382 | + if (controllers[i]) { |
|---|
| 312 | 383 | /* If any devices registered, return success. Else the last error. */ |
|---|
| 313 | | - mtd_device_register(cs553x_mtd[i], NULL, 0); |
|---|
| 384 | + mtd_device_register(nand_to_mtd(&controllers[i]->chip), |
|---|
| 385 | + NULL, 0); |
|---|
| 314 | 386 | err = 0; |
|---|
| 315 | 387 | } |
|---|
| 316 | 388 | } |
|---|
| .. | .. |
|---|
| 325 | 397 | int i; |
|---|
| 326 | 398 | |
|---|
| 327 | 399 | for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { |
|---|
| 328 | | - struct mtd_info *mtd = cs553x_mtd[i]; |
|---|
| 329 | | - struct nand_chip *this; |
|---|
| 330 | | - void __iomem *mmio_base; |
|---|
| 400 | + struct cs553x_nand_controller *controller = controllers[i]; |
|---|
| 401 | + struct nand_chip *this = &controller->chip; |
|---|
| 402 | + struct mtd_info *mtd = nand_to_mtd(this); |
|---|
| 403 | + int ret; |
|---|
| 331 | 404 | |
|---|
| 332 | 405 | if (!mtd) |
|---|
| 333 | 406 | continue; |
|---|
| 334 | 407 | |
|---|
| 335 | | - this = mtd_to_nand(mtd); |
|---|
| 336 | | - mmio_base = this->IO_ADDR_R; |
|---|
| 337 | | - |
|---|
| 338 | 408 | /* Release resources, unregister device */ |
|---|
| 339 | | - nand_release(this); |
|---|
| 409 | + ret = mtd_device_unregister(mtd); |
|---|
| 410 | + WARN_ON(ret); |
|---|
| 411 | + nand_cleanup(this); |
|---|
| 340 | 412 | kfree(mtd->name); |
|---|
| 341 | | - cs553x_mtd[i] = NULL; |
|---|
| 413 | + controllers[i] = NULL; |
|---|
| 342 | 414 | |
|---|
| 343 | 415 | /* unmap physical address */ |
|---|
| 344 | | - iounmap(mmio_base); |
|---|
| 416 | + iounmap(controller->mmio); |
|---|
| 345 | 417 | |
|---|
| 346 | 418 | /* Free the MTD device structure */ |
|---|
| 347 | | - kfree(this); |
|---|
| 419 | + kfree(controller); |
|---|
| 348 | 420 | } |
|---|
| 349 | 421 | } |
|---|
| 350 | 422 | |
|---|