diff options
Diffstat (limited to 'drivers/mtd/nand/cs553x_nand.c')
-rw-r--r-- | drivers/mtd/nand/cs553x_nand.c | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/drivers/mtd/nand/cs553x_nand.c b/drivers/mtd/nand/cs553x_nand.c new file mode 100644 index 00000000000..e0a1d386e58 --- /dev/null +++ b/drivers/mtd/nand/cs553x_nand.c | |||
@@ -0,0 +1,353 @@ | |||
1 | /* | ||
2 | * drivers/mtd/nand/cs553x_nand.c | ||
3 | * | ||
4 | * (C) 2005, 2006 Red Hat Inc. | ||
5 | * | ||
6 | * Author: David Woodhouse <dwmw2@infradead.org> | ||
7 | * Tom Sylla <tom.sylla@amd.com> | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | * | ||
13 | * Overview: | ||
14 | * This is a device driver for the NAND flash controller found on | ||
15 | * the AMD CS5535/CS5536 companion chipsets for the Geode processor. | ||
16 | * | ||
17 | */ | ||
18 | |||
19 | #include <linux/slab.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/pci.h> | ||
24 | #include <linux/mtd/mtd.h> | ||
25 | #include <linux/mtd/nand.h> | ||
26 | #include <linux/mtd/nand_ecc.h> | ||
27 | #include <linux/mtd/partitions.h> | ||
28 | |||
29 | #include <asm/msr.h> | ||
30 | #include <asm/io.h> | ||
31 | |||
32 | #define NR_CS553X_CONTROLLERS 4 | ||
33 | |||
34 | #define MSR_DIVIL_GLD_CAP 0x51400000 /* DIVIL capabilitiies */ | ||
35 | #define CAP_CS5535 0x2df000ULL | ||
36 | #define CAP_CS5536 0x5df500ULL | ||
37 | |||
38 | /* NAND Timing MSRs */ | ||
39 | #define MSR_NANDF_DATA 0x5140001b /* NAND Flash Data Timing MSR */ | ||
40 | #define MSR_NANDF_CTL 0x5140001c /* NAND Flash Control Timing */ | ||
41 | #define MSR_NANDF_RSVD 0x5140001d /* Reserved */ | ||
42 | |||
43 | /* NAND BAR MSRs */ | ||
44 | #define MSR_DIVIL_LBAR_FLSH0 0x51400010 /* Flash Chip Select 0 */ | ||
45 | #define MSR_DIVIL_LBAR_FLSH1 0x51400011 /* Flash Chip Select 1 */ | ||
46 | #define MSR_DIVIL_LBAR_FLSH2 0x51400012 /* Flash Chip Select 2 */ | ||
47 | #define MSR_DIVIL_LBAR_FLSH3 0x51400013 /* Flash Chip Select 3 */ | ||
48 | /* Each made up of... */ | ||
49 | #define FLSH_LBAR_EN (1ULL<<32) | ||
50 | #define FLSH_NOR_NAND (1ULL<<33) /* 1 for NAND */ | ||
51 | #define FLSH_MEM_IO (1ULL<<34) /* 1 for MMIO */ | ||
52 | /* I/O BARs have BASE_ADDR in bits 15:4, IO_MASK in 47:36 */ | ||
53 | /* MMIO BARs have BASE_ADDR in bits 31:12, MEM_MASK in 63:44 */ | ||
54 | |||
55 | /* Pin function selection MSR (IDE vs. flash on the IDE pins) */ | ||
56 | #define MSR_DIVIL_BALL_OPTS 0x51400015 | ||
57 | #define PIN_OPT_IDE (1<<0) /* 0 for flash, 1 for IDE */ | ||
58 | |||
59 | /* Registers within the NAND flash controller BAR -- memory mapped */ | ||
60 | #define MM_NAND_DATA 0x00 /* 0 to 0x7ff, in fact */ | ||
61 | #define MM_NAND_CTL 0x800 /* Any even address 0x800-0x80e */ | ||
62 | #define MM_NAND_IO 0x801 /* Any odd address 0x801-0x80f */ | ||
63 | #define MM_NAND_STS 0x810 | ||
64 | #define MM_NAND_ECC_LSB 0x811 | ||
65 | #define MM_NAND_ECC_MSB 0x812 | ||
66 | #define MM_NAND_ECC_COL 0x813 | ||
67 | #define MM_NAND_LAC 0x814 | ||
68 | #define MM_NAND_ECC_CTL 0x815 | ||
69 | |||
70 | /* Registers within the NAND flash controller BAR -- I/O mapped */ | ||
71 | #define IO_NAND_DATA 0x00 /* 0 to 3, in fact */ | ||
72 | #define IO_NAND_CTL 0x04 | ||
73 | #define IO_NAND_IO 0x05 | ||
74 | #define IO_NAND_STS 0x06 | ||
75 | #define IO_NAND_ECC_CTL 0x08 | ||
76 | #define IO_NAND_ECC_LSB 0x09 | ||
77 | #define IO_NAND_ECC_MSB 0x0a | ||
78 | #define IO_NAND_ECC_COL 0x0b | ||
79 | #define IO_NAND_LAC 0x0c | ||
80 | |||
81 | #define CS_NAND_CTL_DIST_EN (1<<4) /* Enable NAND Distract interrupt */ | ||
82 | #define CS_NAND_CTL_RDY_INT_MASK (1<<3) /* Enable RDY/BUSY# interrupt */ | ||
83 | #define CS_NAND_CTL_ALE (1<<2) | ||
84 | #define CS_NAND_CTL_CLE (1<<1) | ||
85 | #define CS_NAND_CTL_CE (1<<0) /* Keep low; 1 to reset */ | ||
86 | |||
87 | #define CS_NAND_STS_FLASH_RDY (1<<3) | ||
88 | #define CS_NAND_CTLR_BUSY (1<<2) | ||
89 | #define CS_NAND_CMD_COMP (1<<1) | ||
90 | #define CS_NAND_DIST_ST (1<<0) | ||
91 | |||
92 | #define CS_NAND_ECC_PARITY (1<<2) | ||
93 | #define CS_NAND_ECC_CLRECC (1<<1) | ||
94 | #define CS_NAND_ECC_ENECC (1<<0) | ||
95 | |||
96 | static void cs553x_read_buf(struct mtd_info *mtd, u_char *buf, int len) | ||
97 | { | ||
98 | struct nand_chip *this = mtd->priv; | ||
99 | |||
100 | while (unlikely(len > 0x800)) { | ||
101 | memcpy_fromio(buf, this->IO_ADDR_R, 0x800); | ||
102 | buf += 0x800; | ||
103 | len -= 0x800; | ||
104 | } | ||
105 | memcpy_fromio(buf, this->IO_ADDR_R, len); | ||
106 | } | ||
107 | |||
108 | static void cs553x_write_buf(struct mtd_info *mtd, const u_char *buf, int len) | ||
109 | { | ||
110 | struct nand_chip *this = mtd->priv; | ||
111 | |||
112 | while (unlikely(len > 0x800)) { | ||
113 | memcpy_toio(this->IO_ADDR_R, buf, 0x800); | ||
114 | buf += 0x800; | ||
115 | len -= 0x800; | ||
116 | } | ||
117 | memcpy_toio(this->IO_ADDR_R, buf, len); | ||
118 | } | ||
119 | |||
120 | static unsigned char cs553x_read_byte(struct mtd_info *mtd) | ||
121 | { | ||
122 | struct nand_chip *this = mtd->priv; | ||
123 | return readb(this->IO_ADDR_R); | ||
124 | } | ||
125 | |||
126 | static void cs553x_write_byte(struct mtd_info *mtd, u_char byte) | ||
127 | { | ||
128 | struct nand_chip *this = mtd->priv; | ||
129 | int i = 100000; | ||
130 | |||
131 | while (i && readb(this->IO_ADDR_R + MM_NAND_STS) & CS_NAND_CTLR_BUSY) { | ||
132 | udelay(1); | ||
133 | i--; | ||
134 | } | ||
135 | writeb(byte, this->IO_ADDR_W + 0x801); | ||
136 | } | ||
137 | |||
138 | static void cs553x_hwcontrol(struct mtd_info *mtd, int cmd, | ||
139 | unsigned int ctrl) | ||
140 | { | ||
141 | struct nand_chip *this = mtd->priv; | ||
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); | ||
146 | } | ||
147 | if (cmd != NAND_CMD_NONE) | ||
148 | cs553x_write_byte(mtd, cmd); | ||
149 | } | ||
150 | |||
151 | static int cs553x_device_ready(struct mtd_info *mtd) | ||
152 | { | ||
153 | struct nand_chip *this = mtd->priv; | ||
154 | void __iomem *mmio_base = this->IO_ADDR_R; | ||
155 | unsigned char foo = readb(mmio_base + MM_NAND_STS); | ||
156 | |||
157 | return (foo & CS_NAND_STS_FLASH_RDY) && !(foo & CS_NAND_CTLR_BUSY); | ||
158 | } | ||
159 | |||
160 | static void cs_enable_hwecc(struct mtd_info *mtd, int mode) | ||
161 | { | ||
162 | struct nand_chip *this = mtd->priv; | ||
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 | { | ||
170 | uint32_t ecc; | ||
171 | struct nand_chip *this = mtd->priv; | ||
172 | void __iomem *mmio_base = this->IO_ADDR_R; | ||
173 | |||
174 | ecc = readl(mmio_base + MM_NAND_STS); | ||
175 | |||
176 | ecc_code[1] = ecc >> 8; | ||
177 | ecc_code[0] = ecc >> 16; | ||
178 | ecc_code[2] = ecc >> 24; | ||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static struct mtd_info *cs553x_mtd[4]; | ||
183 | |||
184 | static int __init cs553x_init_one(int cs, int mmio, unsigned long adr) | ||
185 | { | ||
186 | int err = 0; | ||
187 | struct nand_chip *this; | ||
188 | struct mtd_info *new_mtd; | ||
189 | |||
190 | printk(KERN_NOTICE "Probing CS553x NAND controller CS#%d at %sIO 0x%08lx\n", cs, mmio?"MM":"P", adr); | ||
191 | |||
192 | if (!mmio) { | ||
193 | printk(KERN_NOTICE "PIO mode not yet implemented for CS553X NAND controller\n"); | ||
194 | return -ENXIO; | ||
195 | } | ||
196 | |||
197 | /* Allocate memory for MTD device structure and private data */ | ||
198 | new_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); | ||
199 | if (!new_mtd) { | ||
200 | printk(KERN_WARNING "Unable to allocate CS553X NAND MTD device structure.\n"); | ||
201 | err = -ENOMEM; | ||
202 | goto out; | ||
203 | } | ||
204 | |||
205 | /* Get pointer to private data */ | ||
206 | this = (struct nand_chip *)(&new_mtd[1]); | ||
207 | |||
208 | /* Initialize structures */ | ||
209 | memset(new_mtd, 0, sizeof(struct mtd_info)); | ||
210 | memset(this, 0, sizeof(struct nand_chip)); | ||
211 | |||
212 | /* Link the private data with the MTD structure */ | ||
213 | new_mtd->priv = this; | ||
214 | new_mtd->owner = THIS_MODULE; | ||
215 | |||
216 | /* map physical address */ | ||
217 | this->IO_ADDR_R = this->IO_ADDR_W = ioremap(adr, 4096); | ||
218 | if (!this->IO_ADDR_R) { | ||
219 | printk(KERN_WARNING "ioremap cs553x NAND @0x%08lx failed\n", adr); | ||
220 | err = -EIO; | ||
221 | goto out_mtd; | ||
222 | } | ||
223 | |||
224 | this->cmd_ctrl = cs553x_hwcontrol; | ||
225 | this->dev_ready = cs553x_device_ready; | ||
226 | this->read_byte = cs553x_read_byte; | ||
227 | this->read_buf = cs553x_read_buf; | ||
228 | this->write_buf = cs553x_write_buf; | ||
229 | |||
230 | this->chip_delay = 0; | ||
231 | |||
232 | this->ecc.mode = NAND_ECC_HW; | ||
233 | this->ecc.size = 256; | ||
234 | this->ecc.bytes = 3; | ||
235 | this->ecc.hwctl = cs_enable_hwecc; | ||
236 | this->ecc.calculate = cs_calculate_ecc; | ||
237 | this->ecc.correct = nand_correct_data; | ||
238 | |||
239 | /* Enable the following for a flash based bad block table */ | ||
240 | this->options = NAND_USE_FLASH_BBT | NAND_NO_AUTOINCR; | ||
241 | |||
242 | /* Scan to find existance of the device */ | ||
243 | if (nand_scan(new_mtd, 1)) { | ||
244 | err = -ENXIO; | ||
245 | goto out_ior; | ||
246 | } | ||
247 | |||
248 | cs553x_mtd[cs] = new_mtd; | ||
249 | goto out; | ||
250 | |||
251 | out_ior: | ||
252 | iounmap((void *)this->IO_ADDR_R); | ||
253 | out_mtd: | ||
254 | kfree(new_mtd); | ||
255 | out: | ||
256 | return err; | ||
257 | } | ||
258 | |||
259 | static int is_geode(void) | ||
260 | { | ||
261 | /* These are the CPUs which will have a CS553[56] companion chip */ | ||
262 | if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD && | ||
263 | boot_cpu_data.x86 == 5 && | ||
264 | boot_cpu_data.x86_model == 10) | ||
265 | return 1; /* Geode LX */ | ||
266 | |||
267 | if ((boot_cpu_data.x86_vendor == X86_VENDOR_NSC || | ||
268 | boot_cpu_data.x86_vendor == X86_VENDOR_CYRIX) && | ||
269 | boot_cpu_data.x86 == 5 && | ||
270 | boot_cpu_data.x86_model == 5) | ||
271 | return 1; /* Geode GX (née GX2) */ | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static int __init cs553x_init(void) | ||
277 | { | ||
278 | int err = -ENXIO; | ||
279 | int i; | ||
280 | uint64_t val; | ||
281 | |||
282 | /* If the CPU isn't a Geode GX or LX, abort */ | ||
283 | if (!is_geode()) | ||
284 | return -ENXIO; | ||
285 | |||
286 | /* If it doesn't have the CS553[56], abort */ | ||
287 | rdmsrl(MSR_DIVIL_GLD_CAP, val); | ||
288 | val &= ~0xFFULL; | ||
289 | if (val != CAP_CS5535 && val != CAP_CS5536) | ||
290 | return -ENXIO; | ||
291 | |||
292 | /* If it doesn't have the NAND controller enabled, abort */ | ||
293 | rdmsrl(MSR_DIVIL_BALL_OPTS, val); | ||
294 | if (val & 1) { | ||
295 | printk(KERN_INFO "CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS.\n"); | ||
296 | return -ENXIO; | ||
297 | } | ||
298 | |||
299 | for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { | ||
300 | rdmsrl(MSR_DIVIL_LBAR_FLSH0 + i, val); | ||
301 | |||
302 | if ((val & (FLSH_LBAR_EN|FLSH_NOR_NAND)) == (FLSH_LBAR_EN|FLSH_NOR_NAND)) | ||
303 | err = cs553x_init_one(i, !!(val & FLSH_MEM_IO), val & 0xFFFFFFFF); | ||
304 | } | ||
305 | |||
306 | /* Register all devices together here. This means we can easily hack it to | ||
307 | do mtdconcat etc. if we want to. */ | ||
308 | for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { | ||
309 | if (cs553x_mtd[i]) { | ||
310 | add_mtd_device(cs553x_mtd[i]); | ||
311 | |||
312 | /* If any devices registered, return success. Else the last error. */ | ||
313 | err = 0; | ||
314 | } | ||
315 | } | ||
316 | |||
317 | return err; | ||
318 | } | ||
319 | |||
320 | module_init(cs553x_init); | ||
321 | |||
322 | static void __exit cs553x_cleanup(void) | ||
323 | { | ||
324 | int i; | ||
325 | |||
326 | for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { | ||
327 | struct mtd_info *mtd = cs553x_mtd[i]; | ||
328 | struct nand_chip *this; | ||
329 | void __iomem *mmio_base; | ||
330 | |||
331 | if (!mtd) | ||
332 | break; | ||
333 | |||
334 | this = cs553x_mtd[i]->priv; | ||
335 | mmio_base = this->IO_ADDR_R; | ||
336 | |||
337 | /* Release resources, unregister device */ | ||
338 | nand_release(cs553x_mtd[i]); | ||
339 | cs553x_mtd[i] = NULL; | ||
340 | |||
341 | /* unmap physical adress */ | ||
342 | iounmap(mmio_base); | ||
343 | |||
344 | /* Free the MTD device structure */ | ||
345 | kfree(mtd); | ||
346 | } | ||
347 | } | ||
348 | |||
349 | module_exit(cs553x_cleanup); | ||
350 | |||
351 | MODULE_LICENSE("GPL"); | ||
352 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); | ||
353 | MODULE_DESCRIPTION("NAND controller driver for AMD CS5535/CS5536 companion chip"); | ||