.. | .. |
---|
| 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 | |
---|