Commit | Line | Data |
---|---|---|
ce4c61f1 | 1 | /* |
ce4c61f1 | 2 | * Overview: |
a808ad3b | 3 | * Platform independent driver for NDFC (NanD Flash Controller) |
ce4c61f1 TG |
4 | * integrated into EP440 cores |
5 | * | |
a808ad3b SM |
6 | * Ported to an OF platform driver by Sean MacLennan |
7 | * | |
8 | * The NDFC supports multiple chips, but this driver only supports a | |
9 | * single chip since I do not have access to any boards with | |
10 | * multiple chips. | |
11 | * | |
ce4c61f1 TG |
12 | * Author: Thomas Gleixner |
13 | * | |
14 | * Copyright 2006 IBM | |
a808ad3b SM |
15 | * Copyright 2008 PIKA Technologies |
16 | * Sean MacLennan <smaclennan@pikatech.com> | |
ce4c61f1 TG |
17 | * |
18 | * This program is free software; you can redistribute it and/or modify it | |
19 | * under the terms of the GNU General Public License as published by the | |
20 | * Free Software Foundation; either version 2 of the License, or (at your | |
21 | * option) any later version. | |
22 | * | |
23 | */ | |
24 | #include <linux/module.h> | |
d4092d76 | 25 | #include <linux/mtd/rawnand.h> |
ce4c61f1 TG |
26 | #include <linux/mtd/nand_ecc.h> |
27 | #include <linux/mtd/partitions.h> | |
28 | #include <linux/mtd/ndfc.h> | |
5a0e3ad6 | 29 | #include <linux/slab.h> |
ce4c61f1 | 30 | #include <linux/mtd/mtd.h> |
5af50730 | 31 | #include <linux/of_address.h> |
a808ad3b | 32 | #include <linux/of_platform.h> |
ce4c61f1 | 33 | #include <asm/io.h> |
ce4c61f1 | 34 | |
410fe2f0 | 35 | #define NDFC_MAX_CS 4 |
ce4c61f1 TG |
36 | |
37 | struct ndfc_controller { | |
2dc11581 | 38 | struct platform_device *ofdev; |
a808ad3b | 39 | void __iomem *ndfcbase; |
a808ad3b SM |
40 | struct nand_chip chip; |
41 | int chip_select; | |
7da45139 | 42 | struct nand_controller ndfc_control; |
ce4c61f1 TG |
43 | }; |
44 | ||
410fe2f0 | 45 | static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS]; |
ce4c61f1 | 46 | |
758b56f5 | 47 | static void ndfc_select_chip(struct nand_chip *nchip, int chip) |
ce4c61f1 TG |
48 | { |
49 | uint32_t ccr; | |
d699ed25 | 50 | struct ndfc_controller *ndfc = nand_get_controller_data(nchip); |
ce4c61f1 | 51 | |
a808ad3b | 52 | ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); |
ce4c61f1 TG |
53 | if (chip >= 0) { |
54 | ccr &= ~NDFC_CCR_BS_MASK; | |
a808ad3b | 55 | ccr |= NDFC_CCR_BS(chip + ndfc->chip_select); |
ce4c61f1 TG |
56 | } else |
57 | ccr |= NDFC_CCR_RESET_CE; | |
a808ad3b | 58 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
ce4c61f1 TG |
59 | } |
60 | ||
7abd3ef9 | 61 | static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) |
ce4c61f1 | 62 | { |
4bd4ebcc | 63 | struct nand_chip *chip = mtd_to_nand(mtd); |
d699ed25 | 64 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
ce4c61f1 | 65 | |
7abd3ef9 TG |
66 | if (cmd == NAND_CMD_NONE) |
67 | return; | |
68 | ||
69 | if (ctrl & NAND_CLE) | |
1794c130 | 70 | writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD); |
7abd3ef9 | 71 | else |
1794c130 | 72 | writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE); |
ce4c61f1 TG |
73 | } |
74 | ||
75 | static int ndfc_ready(struct mtd_info *mtd) | |
76 | { | |
4bd4ebcc | 77 | struct nand_chip *chip = mtd_to_nand(mtd); |
d699ed25 | 78 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
ce4c61f1 | 79 | |
a808ad3b | 80 | return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; |
ce4c61f1 TG |
81 | } |
82 | ||
ec47636c | 83 | static void ndfc_enable_hwecc(struct nand_chip *chip, int mode) |
ce4c61f1 TG |
84 | { |
85 | uint32_t ccr; | |
d699ed25 | 86 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
ce4c61f1 | 87 | |
a808ad3b | 88 | ccr = in_be32(ndfc->ndfcbase + NDFC_CCR); |
ce4c61f1 | 89 | ccr |= NDFC_CCR_RESET_ECC; |
a808ad3b | 90 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
ce4c61f1 TG |
91 | wmb(); |
92 | } | |
93 | ||
af37d2c3 | 94 | static int ndfc_calculate_ecc(struct nand_chip *chip, |
ce4c61f1 TG |
95 | const u_char *dat, u_char *ecc_code) |
96 | { | |
d699ed25 | 97 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
ce4c61f1 TG |
98 | uint32_t ecc; |
99 | uint8_t *p = (uint8_t *)&ecc; | |
100 | ||
101 | wmb(); | |
a808ad3b SM |
102 | ecc = in_be32(ndfc->ndfcbase + NDFC_ECC); |
103 | /* The NDFC uses Smart Media (SMC) bytes order */ | |
76c23c32 FK |
104 | ecc_code[0] = p[1]; |
105 | ecc_code[1] = p[2]; | |
ce4c61f1 TG |
106 | ecc_code[2] = p[3]; |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
111 | /* | |
112 | * Speedups for buffer read/write/verify | |
113 | * | |
114 | * NDFC allows 32bit read/write of data. So we can speed up the buffer | |
115 | * functions. No further checking, as nand_base will always read/write | |
116 | * page aligned. | |
117 | */ | |
7e534323 | 118 | static void ndfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) |
ce4c61f1 | 119 | { |
d699ed25 | 120 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
ce4c61f1 TG |
121 | uint32_t *p = (uint32_t *) buf; |
122 | ||
123 | for(;len > 0; len -= 4) | |
a808ad3b | 124 | *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA); |
ce4c61f1 TG |
125 | } |
126 | ||
c0739d85 | 127 | static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) |
ce4c61f1 | 128 | { |
d699ed25 | 129 | struct ndfc_controller *ndfc = nand_get_controller_data(chip); |
ce4c61f1 TG |
130 | uint32_t *p = (uint32_t *) buf; |
131 | ||
132 | for(;len > 0; len -= 4) | |
a808ad3b | 133 | out_be32(ndfc->ndfcbase + NDFC_DATA, *p++); |
ce4c61f1 TG |
134 | } |
135 | ||
ce4c61f1 TG |
136 | /* |
137 | * Initialize chip structure | |
138 | */ | |
a808ad3b SM |
139 | static int ndfc_chip_init(struct ndfc_controller *ndfc, |
140 | struct device_node *node) | |
ce4c61f1 | 141 | { |
a808ad3b SM |
142 | struct device_node *flash_np; |
143 | struct nand_chip *chip = &ndfc->chip; | |
ca921b53 | 144 | struct mtd_info *mtd = nand_to_mtd(chip); |
a808ad3b | 145 | int ret; |
ce4c61f1 TG |
146 | |
147 | chip->IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; | |
148 | chip->IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; | |
7abd3ef9 | 149 | chip->cmd_ctrl = ndfc_hwcontrol; |
ce4c61f1 TG |
150 | chip->dev_ready = ndfc_ready; |
151 | chip->select_chip = ndfc_select_chip; | |
152 | chip->chip_delay = 50; | |
ce4c61f1 TG |
153 | chip->controller = &ndfc->ndfc_control; |
154 | chip->read_buf = ndfc_read_buf; | |
155 | chip->write_buf = ndfc_write_buf; | |
6dfc6d25 TG |
156 | chip->ecc.correct = nand_correct_data; |
157 | chip->ecc.hwctl = ndfc_enable_hwecc; | |
158 | chip->ecc.calculate = ndfc_calculate_ecc; | |
159 | chip->ecc.mode = NAND_ECC_HW; | |
160 | chip->ecc.size = 256; | |
161 | chip->ecc.bytes = 3; | |
6a918bad | 162 | chip->ecc.strength = 1; |
d699ed25 | 163 | nand_set_controller_data(chip, ndfc); |
ce4c61f1 | 164 | |
ca921b53 | 165 | mtd->dev.parent = &ndfc->ofdev->dev; |
ce4c61f1 | 166 | |
a808ad3b SM |
167 | flash_np = of_get_next_child(node, NULL); |
168 | if (!flash_np) | |
ce4c61f1 | 169 | return -ENODEV; |
a61ae81a | 170 | nand_set_flash_node(chip, flash_np); |
a808ad3b | 171 | |
a9fdba0b RH |
172 | mtd->name = kasprintf(GFP_KERNEL, "%s.%pOFn", dev_name(&ndfc->ofdev->dev), |
173 | flash_np); | |
ca921b53 | 174 | if (!mtd->name) { |
a808ad3b SM |
175 | ret = -ENOMEM; |
176 | goto err; | |
ce4c61f1 TG |
177 | } |
178 | ||
00ad378f | 179 | ret = nand_scan(chip, 1); |
a808ad3b SM |
180 | if (ret) |
181 | goto err; | |
ce4c61f1 | 182 | |
ca921b53 | 183 | ret = mtd_device_register(mtd, NULL, 0); |
ce4c61f1 | 184 | |
a808ad3b SM |
185 | err: |
186 | of_node_put(flash_np); | |
187 | if (ret) | |
ca921b53 | 188 | kfree(mtd->name); |
a808ad3b | 189 | return ret; |
ce4c61f1 TG |
190 | } |
191 | ||
06f25510 | 192 | static int ndfc_probe(struct platform_device *ofdev) |
ce4c61f1 | 193 | { |
410fe2f0 | 194 | struct ndfc_controller *ndfc; |
766f271a | 195 | const __be32 *reg; |
a808ad3b | 196 | u32 ccr; |
5828c608 DC |
197 | u32 cs; |
198 | int err, len; | |
a808ad3b SM |
199 | |
200 | /* Read the reg property to get the chip select */ | |
61c7a080 | 201 | reg = of_get_property(ofdev->dev.of_node, "reg", &len); |
a808ad3b SM |
202 | if (reg == NULL || len != 12) { |
203 | dev_err(&ofdev->dev, "unable read reg property (%d)\n", len); | |
204 | return -ENOENT; | |
205 | } | |
410fe2f0 FR |
206 | |
207 | cs = be32_to_cpu(reg[0]); | |
208 | if (cs >= NDFC_MAX_CS) { | |
209 | dev_err(&ofdev->dev, "invalid CS number (%d)\n", cs); | |
210 | return -EINVAL; | |
211 | } | |
212 | ||
213 | ndfc = &ndfc_ctrl[cs]; | |
214 | ndfc->chip_select = cs; | |
215 | ||
7da45139 | 216 | nand_controller_init(&ndfc->ndfc_control); |
410fe2f0 FR |
217 | ndfc->ofdev = ofdev; |
218 | dev_set_drvdata(&ofdev->dev, ndfc); | |
a808ad3b | 219 | |
61c7a080 | 220 | ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0); |
ce4c61f1 | 221 | if (!ndfc->ndfcbase) { |
a808ad3b | 222 | dev_err(&ofdev->dev, "failed to get memory\n"); |
ce4c61f1 TG |
223 | return -EIO; |
224 | } | |
225 | ||
a808ad3b | 226 | ccr = NDFC_CCR_BS(ndfc->chip_select); |
ce4c61f1 | 227 | |
a808ad3b | 228 | /* It is ok if ccr does not exist - just default to 0 */ |
61c7a080 | 229 | reg = of_get_property(ofdev->dev.of_node, "ccr", NULL); |
a808ad3b | 230 | if (reg) |
766f271a | 231 | ccr |= be32_to_cpup(reg); |
ce4c61f1 | 232 | |
a808ad3b | 233 | out_be32(ndfc->ndfcbase + NDFC_CCR, ccr); |
ce4c61f1 | 234 | |
a808ad3b | 235 | /* Set the bank settings if given */ |
61c7a080 | 236 | reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL); |
a808ad3b SM |
237 | if (reg) { |
238 | int offset = NDFC_BCFG0 + (ndfc->chip_select << 2); | |
766f271a | 239 | out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg)); |
a808ad3b SM |
240 | } |
241 | ||
61c7a080 | 242 | err = ndfc_chip_init(ndfc, ofdev->dev.of_node); |
a808ad3b SM |
243 | if (err) { |
244 | iounmap(ndfc->ndfcbase); | |
245 | return err; | |
246 | } | |
ce4c61f1 TG |
247 | |
248 | return 0; | |
249 | } | |
250 | ||
810b7e06 | 251 | static int ndfc_remove(struct platform_device *ofdev) |
ce4c61f1 | 252 | { |
a808ad3b | 253 | struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev); |
ca921b53 | 254 | struct mtd_info *mtd = nand_to_mtd(&ndfc->chip); |
ce4c61f1 | 255 | |
59ac276f | 256 | nand_release(&ndfc->chip); |
ca921b53 | 257 | kfree(mtd->name); |
ce4c61f1 | 258 | |
ce4c61f1 TG |
259 | return 0; |
260 | } | |
261 | ||
a808ad3b SM |
262 | static const struct of_device_id ndfc_match[] = { |
263 | { .compatible = "ibm,ndfc", }, | |
264 | {} | |
ce4c61f1 | 265 | }; |
a808ad3b | 266 | MODULE_DEVICE_TABLE(of, ndfc_match); |
ce4c61f1 | 267 | |
1c48a5c9 | 268 | static struct platform_driver ndfc_driver = { |
a808ad3b | 269 | .driver = { |
4018294b | 270 | .name = "ndfc", |
4018294b | 271 | .of_match_table = ndfc_match, |
ce4c61f1 | 272 | }, |
a808ad3b | 273 | .probe = ndfc_probe, |
5153b88c | 274 | .remove = ndfc_remove, |
ce4c61f1 TG |
275 | }; |
276 | ||
f99640de | 277 | module_platform_driver(ndfc_driver); |
ce4c61f1 TG |
278 | |
279 | MODULE_LICENSE("GPL"); | |
280 | MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>"); | |
a808ad3b | 281 | MODULE_DESCRIPTION("OF Platform driver for NDFC"); |