Commit | Line | Data |
---|---|---|
aa62e90f JY |
1 | /* |
2 | * linux/arch/arm/mach-omap2/gpmc-onenand.c | |
3 | * | |
4 | * Copyright (C) 2006 - 2009 Nokia Corporation | |
5 | * Contacts: Juha Yrjola | |
6 | * Tony Lindgren | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | */ | |
12 | ||
d44b28c4 | 13 | #include <linux/string.h> |
aa62e90f JY |
14 | #include <linux/kernel.h> |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/mtd/onenand_regs.h> | |
17 | #include <linux/io.h> | |
2203747c | 18 | #include <linux/platform_data/mtd-onenand-omap2.h> |
46376884 | 19 | #include <linux/err.h> |
aa62e90f JY |
20 | |
21 | #include <asm/mach/flash.h> | |
22 | ||
3ef5d007 | 23 | #include "gpmc.h" |
dbc04161 | 24 | #include "soc.h" |
b6ab13e7 | 25 | #include "gpmc-onenand.h" |
dbc04161 | 26 | |
681988ba AM |
27 | #define ONENAND_IO_SIZE SZ_128K |
28 | ||
46376884 AM |
29 | #define ONENAND_FLAG_SYNCREAD (1 << 0) |
30 | #define ONENAND_FLAG_SYNCWRITE (1 << 1) | |
31 | #define ONENAND_FLAG_HF (1 << 2) | |
32 | #define ONENAND_FLAG_VHF (1 << 3) | |
33 | ||
34 | static unsigned onenand_flags; | |
35 | static unsigned latency; | |
36 | static int fclk_offset; | |
37 | ||
aa62e90f JY |
38 | static struct omap_onenand_platform_data *gpmc_onenand_data; |
39 | ||
681988ba AM |
40 | static struct resource gpmc_onenand_resource = { |
41 | .flags = IORESOURCE_MEM, | |
42 | }; | |
43 | ||
aa62e90f JY |
44 | static struct platform_device gpmc_onenand_device = { |
45 | .name = "omap2-onenand", | |
46 | .id = -1, | |
681988ba AM |
47 | .num_resources = 1, |
48 | .resource = &gpmc_onenand_resource, | |
aa62e90f JY |
49 | }; |
50 | ||
46376884 | 51 | static struct gpmc_timings omap2_onenand_calc_async_timings(void) |
aa62e90f JY |
52 | { |
53 | struct gpmc_timings t; | |
54 | ||
55 | const int t_cer = 15; | |
56 | const int t_avdp = 12; | |
57 | const int t_aavdh = 7; | |
58 | const int t_ce = 76; | |
59 | const int t_aa = 76; | |
60 | const int t_oe = 20; | |
61 | const int t_cez = 20; /* max of t_cez, t_oez */ | |
62 | const int t_ds = 30; | |
63 | const int t_wpl = 40; | |
64 | const int t_wph = 30; | |
65 | ||
66 | memset(&t, 0, sizeof(t)); | |
67 | t.sync_clk = 0; | |
68 | t.cs_on = 0; | |
69 | t.adv_on = 0; | |
70 | ||
71 | /* Read */ | |
72 | t.adv_rd_off = gpmc_round_ns_to_ticks(max_t(int, t_avdp, t_cer)); | |
73 | t.oe_on = t.adv_rd_off + gpmc_round_ns_to_ticks(t_aavdh); | |
74 | t.access = t.adv_on + gpmc_round_ns_to_ticks(t_aa); | |
75 | t.access = max_t(int, t.access, t.cs_on + gpmc_round_ns_to_ticks(t_ce)); | |
76 | t.access = max_t(int, t.access, t.oe_on + gpmc_round_ns_to_ticks(t_oe)); | |
77 | t.oe_off = t.access + gpmc_round_ns_to_ticks(1); | |
78 | t.cs_rd_off = t.oe_off; | |
79 | t.rd_cycle = t.cs_rd_off + gpmc_round_ns_to_ticks(t_cez); | |
80 | ||
81 | /* Write */ | |
82 | t.adv_wr_off = t.adv_rd_off; | |
83 | t.we_on = t.oe_on; | |
84 | if (cpu_is_omap34xx()) { | |
85 | t.wr_data_mux_bus = t.we_on; | |
86 | t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds); | |
87 | } | |
88 | t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); | |
89 | t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph); | |
90 | t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez); | |
91 | ||
46376884 AM |
92 | return t; |
93 | } | |
94 | ||
95 | static int gpmc_set_async_mode(int cs, struct gpmc_timings *t) | |
96 | { | |
aa62e90f JY |
97 | /* Configure GPMC for asynchronous read */ |
98 | gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, | |
99 | GPMC_CONFIG1_DEVICESIZE_16 | | |
100 | GPMC_CONFIG1_MUXADDDATA); | |
101 | ||
46376884 AM |
102 | return gpmc_cs_set_timings(cs, t); |
103 | } | |
104 | ||
105 | static void omap2_onenand_set_async_mode(void __iomem *onenand_base) | |
106 | { | |
107 | u32 reg; | |
6d453e84 AH |
108 | |
109 | /* Ensure sync read and sync write are disabled */ | |
110 | reg = readw(onenand_base + ONENAND_REG_SYS_CFG1); | |
111 | reg &= ~ONENAND_SYS_CFG1_SYNC_READ & ~ONENAND_SYS_CFG1_SYNC_WRITE; | |
112 | writew(reg, onenand_base + ONENAND_REG_SYS_CFG1); | |
aa62e90f JY |
113 | } |
114 | ||
46376884 | 115 | static void set_onenand_cfg(void __iomem *onenand_base) |
aa62e90f JY |
116 | { |
117 | u32 reg; | |
118 | ||
119 | reg = readw(onenand_base + ONENAND_REG_SYS_CFG1); | |
120 | reg &= ~((0x7 << ONENAND_SYS_CFG1_BRL_SHIFT) | (0x7 << 9)); | |
121 | reg |= (latency << ONENAND_SYS_CFG1_BRL_SHIFT) | | |
122 | ONENAND_SYS_CFG1_BL_16; | |
46376884 | 123 | if (onenand_flags & ONENAND_FLAG_SYNCREAD) |
aa62e90f JY |
124 | reg |= ONENAND_SYS_CFG1_SYNC_READ; |
125 | else | |
126 | reg &= ~ONENAND_SYS_CFG1_SYNC_READ; | |
46376884 | 127 | if (onenand_flags & ONENAND_FLAG_SYNCWRITE) |
aa62e90f JY |
128 | reg |= ONENAND_SYS_CFG1_SYNC_WRITE; |
129 | else | |
130 | reg &= ~ONENAND_SYS_CFG1_SYNC_WRITE; | |
46376884 | 131 | if (onenand_flags & ONENAND_FLAG_HF) |
aa62e90f JY |
132 | reg |= ONENAND_SYS_CFG1_HF; |
133 | else | |
134 | reg &= ~ONENAND_SYS_CFG1_HF; | |
46376884 | 135 | if (onenand_flags & ONENAND_FLAG_VHF) |
1435ca0f AH |
136 | reg |= ONENAND_SYS_CFG1_VHF; |
137 | else | |
138 | reg &= ~ONENAND_SYS_CFG1_VHF; | |
aa62e90f JY |
139 | writew(reg, onenand_base + ONENAND_REG_SYS_CFG1); |
140 | } | |
141 | ||
5714b7ed | 142 | static int omap2_onenand_get_freq(struct omap_onenand_platform_data *cfg, |
757ef791 | 143 | void __iomem *onenand_base) |
5714b7ed AH |
144 | { |
145 | u16 ver = readw(onenand_base + ONENAND_REG_VERSION_ID); | |
757ef791 | 146 | int freq; |
5714b7ed AH |
147 | |
148 | switch ((ver >> 4) & 0xf) { | |
149 | case 0: | |
150 | freq = 40; | |
151 | break; | |
152 | case 1: | |
153 | freq = 54; | |
154 | break; | |
155 | case 2: | |
156 | freq = 66; | |
157 | break; | |
158 | case 3: | |
159 | freq = 83; | |
160 | break; | |
161 | case 4: | |
162 | freq = 104; | |
163 | break; | |
164 | default: | |
165 | freq = 54; | |
166 | break; | |
167 | } | |
168 | ||
169 | return freq; | |
170 | } | |
171 | ||
46376884 AM |
172 | static struct gpmc_timings |
173 | omap2_onenand_calc_sync_timings(struct omap_onenand_platform_data *cfg, | |
757ef791 | 174 | int freq) |
aa62e90f JY |
175 | { |
176 | struct gpmc_timings t; | |
177 | const int t_cer = 15; | |
178 | const int t_avdp = 12; | |
179 | const int t_cez = 20; /* max of t_cez, t_oez */ | |
180 | const int t_ds = 30; | |
181 | const int t_wpl = 40; | |
182 | const int t_wph = 30; | |
183 | int min_gpmc_clk_period, t_ces, t_avds, t_avdh, t_ach, t_aavdh, t_rdyo; | |
46376884 AM |
184 | int div, fclk_offset_ns, gpmc_clk_ns; |
185 | int ticks_cez; | |
aa62e90f | 186 | |
46376884 AM |
187 | if (cfg->flags & ONENAND_SYNC_READ) |
188 | onenand_flags = ONENAND_FLAG_SYNCREAD; | |
189 | else if (cfg->flags & ONENAND_SYNC_READWRITE) | |
190 | onenand_flags = ONENAND_FLAG_SYNCREAD | ONENAND_FLAG_SYNCWRITE; | |
aa62e90f JY |
191 | |
192 | switch (freq) { | |
4931445b AH |
193 | case 104: |
194 | min_gpmc_clk_period = 9600; /* 104 MHz */ | |
195 | t_ces = 3; | |
196 | t_avds = 4; | |
197 | t_avdh = 2; | |
198 | t_ach = 3; | |
199 | t_aavdh = 6; | |
1435ca0f | 200 | t_rdyo = 6; |
4931445b | 201 | break; |
aa62e90f | 202 | case 83: |
a3551f5b | 203 | min_gpmc_clk_period = 12000; /* 83 MHz */ |
aa62e90f JY |
204 | t_ces = 5; |
205 | t_avds = 4; | |
206 | t_avdh = 2; | |
207 | t_ach = 6; | |
208 | t_aavdh = 6; | |
209 | t_rdyo = 9; | |
210 | break; | |
211 | case 66: | |
a3551f5b | 212 | min_gpmc_clk_period = 15000; /* 66 MHz */ |
aa62e90f JY |
213 | t_ces = 6; |
214 | t_avds = 5; | |
215 | t_avdh = 2; | |
216 | t_ach = 6; | |
217 | t_aavdh = 6; | |
218 | t_rdyo = 11; | |
219 | break; | |
220 | default: | |
a3551f5b | 221 | min_gpmc_clk_period = 18500; /* 54 MHz */ |
aa62e90f JY |
222 | t_ces = 7; |
223 | t_avds = 7; | |
224 | t_avdh = 7; | |
225 | t_ach = 9; | |
226 | t_aavdh = 7; | |
227 | t_rdyo = 15; | |
46376884 | 228 | onenand_flags &= ~ONENAND_FLAG_SYNCWRITE; |
aa62e90f JY |
229 | break; |
230 | } | |
231 | ||
1b47ca1a | 232 | div = gpmc_calc_divider(min_gpmc_clk_period); |
aa62e90f JY |
233 | gpmc_clk_ns = gpmc_ticks_to_ns(div); |
234 | if (gpmc_clk_ns < 15) /* >66Mhz */ | |
46376884 AM |
235 | onenand_flags |= ONENAND_FLAG_HF; |
236 | else | |
237 | onenand_flags &= ~ONENAND_FLAG_HF; | |
1435ca0f | 238 | if (gpmc_clk_ns < 12) /* >83Mhz */ |
46376884 AM |
239 | onenand_flags |= ONENAND_FLAG_VHF; |
240 | else | |
241 | onenand_flags &= ~ONENAND_FLAG_VHF; | |
242 | if (onenand_flags & ONENAND_FLAG_VHF) | |
1435ca0f | 243 | latency = 8; |
46376884 | 244 | else if (onenand_flags & ONENAND_FLAG_HF) |
aa62e90f JY |
245 | latency = 6; |
246 | else if (gpmc_clk_ns >= 25) /* 40 MHz*/ | |
247 | latency = 3; | |
248 | else | |
249 | latency = 4; | |
250 | ||
46376884 AM |
251 | /* Set synchronous read timings */ |
252 | memset(&t, 0, sizeof(t)); | |
aa62e90f JY |
253 | |
254 | if (div == 1) { | |
559d94b0 AM |
255 | t.bool_timings.cs_extra_delay = true; |
256 | t.bool_timings.adv_extra_delay = true; | |
257 | t.bool_timings.oe_extra_delay = true; | |
258 | t.bool_timings.we_extra_delay = true; | |
aa62e90f JY |
259 | } |
260 | ||
aa62e90f JY |
261 | t.sync_clk = min_gpmc_clk_period; |
262 | t.cs_on = 0; | |
263 | t.adv_on = 0; | |
264 | fclk_offset_ns = gpmc_round_ns_to_ticks(max_t(int, t_ces, t_avds)); | |
265 | fclk_offset = gpmc_ns_to_ticks(fclk_offset_ns); | |
266 | t.page_burst_access = gpmc_clk_ns; | |
267 | ||
268 | /* Read */ | |
269 | t.adv_rd_off = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_avdh)); | |
270 | t.oe_on = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_ach)); | |
1435ca0f AH |
271 | /* Force at least 1 clk between AVD High to OE Low */ |
272 | if (t.oe_on <= t.adv_rd_off) | |
273 | t.oe_on = t.adv_rd_off + gpmc_round_ns_to_ticks(1); | |
aa62e90f JY |
274 | t.access = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div); |
275 | t.oe_off = t.access + gpmc_round_ns_to_ticks(1); | |
276 | t.cs_rd_off = t.oe_off; | |
277 | ticks_cez = ((gpmc_ns_to_ticks(t_cez) + div - 1) / div) * div; | |
278 | t.rd_cycle = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div + | |
279 | ticks_cez); | |
280 | ||
559d94b0 AM |
281 | t.clk_activation = fclk_offset_ns; |
282 | ||
aa62e90f | 283 | /* Write */ |
46376884 | 284 | if (onenand_flags & ONENAND_FLAG_SYNCWRITE) { |
aa62e90f JY |
285 | t.adv_wr_off = t.adv_rd_off; |
286 | t.we_on = 0; | |
287 | t.we_off = t.cs_rd_off; | |
288 | t.cs_wr_off = t.cs_rd_off; | |
289 | t.wr_cycle = t.rd_cycle; | |
290 | if (cpu_is_omap34xx()) { | |
291 | t.wr_data_mux_bus = gpmc_ticks_to_ns(fclk_offset + | |
a3551f5b AH |
292 | gpmc_ps_to_ticks(min_gpmc_clk_period + |
293 | t_rdyo * 1000)); | |
aa62e90f JY |
294 | t.wr_access = t.access; |
295 | } | |
296 | } else { | |
297 | t.adv_wr_off = gpmc_round_ns_to_ticks(max_t(int, | |
298 | t_avdp, t_cer)); | |
299 | t.we_on = t.adv_wr_off + gpmc_round_ns_to_ticks(t_aavdh); | |
300 | t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); | |
301 | t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(t_wph); | |
302 | t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(t_cez); | |
303 | if (cpu_is_omap34xx()) { | |
304 | t.wr_data_mux_bus = t.we_on; | |
305 | t.wr_access = t.we_on + gpmc_round_ns_to_ticks(t_ds); | |
306 | } | |
307 | } | |
308 | ||
46376884 AM |
309 | return t; |
310 | } | |
311 | ||
312 | static int gpmc_set_sync_mode(int cs, struct gpmc_timings *t) | |
313 | { | |
314 | unsigned sync_read = onenand_flags & ONENAND_FLAG_SYNCREAD; | |
315 | unsigned sync_write = onenand_flags & ONENAND_FLAG_SYNCWRITE; | |
316 | ||
aa62e90f JY |
317 | /* Configure GPMC for synchronous read */ |
318 | gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, | |
319 | GPMC_CONFIG1_WRAPBURST_SUPP | | |
320 | GPMC_CONFIG1_READMULTIPLE_SUPP | | |
321 | (sync_read ? GPMC_CONFIG1_READTYPE_SYNC : 0) | | |
322 | (sync_write ? GPMC_CONFIG1_WRITEMULTIPLE_SUPP : 0) | | |
323 | (sync_write ? GPMC_CONFIG1_WRITETYPE_SYNC : 0) | | |
aa62e90f JY |
324 | GPMC_CONFIG1_PAGE_LEN(2) | |
325 | (cpu_is_omap34xx() ? 0 : | |
326 | (GPMC_CONFIG1_WAIT_READ_MON | | |
327 | GPMC_CONFIG1_WAIT_PIN_SEL(0))) | | |
328 | GPMC_CONFIG1_DEVICESIZE_16 | | |
329 | GPMC_CONFIG1_DEVICETYPE_NOR | | |
330 | GPMC_CONFIG1_MUXADDDATA); | |
331 | ||
46376884 AM |
332 | return gpmc_cs_set_timings(cs, t); |
333 | } | |
334 | ||
335 | static int omap2_onenand_setup_async(void __iomem *onenand_base) | |
336 | { | |
337 | struct gpmc_timings t; | |
338 | int ret; | |
339 | ||
340 | omap2_onenand_set_async_mode(onenand_base); | |
341 | ||
342 | t = omap2_onenand_calc_async_timings(); | |
343 | ||
344 | ret = gpmc_set_async_mode(gpmc_onenand_data->cs, &t); | |
345 | if (IS_ERR_VALUE(ret)) | |
346 | return ret; | |
347 | ||
348 | omap2_onenand_set_async_mode(onenand_base); | |
349 | ||
350 | return 0; | |
351 | } | |
352 | ||
353 | static int omap2_onenand_setup_sync(void __iomem *onenand_base, int *freq_ptr) | |
354 | { | |
355 | int ret, freq = *freq_ptr; | |
356 | struct gpmc_timings t; | |
46376884 AM |
357 | |
358 | if (!freq) { | |
359 | /* Very first call freq is not known */ | |
757ef791 | 360 | freq = omap2_onenand_get_freq(gpmc_onenand_data, onenand_base); |
46376884 AM |
361 | set_onenand_cfg(onenand_base); |
362 | } | |
363 | ||
757ef791 | 364 | t = omap2_onenand_calc_sync_timings(gpmc_onenand_data, freq); |
aa62e90f | 365 | |
46376884 AM |
366 | ret = gpmc_set_sync_mode(gpmc_onenand_data->cs, &t); |
367 | if (IS_ERR_VALUE(ret)) | |
368 | return ret; | |
369 | ||
370 | set_onenand_cfg(onenand_base); | |
aa62e90f | 371 | |
3ad2d861 AH |
372 | *freq_ptr = freq; |
373 | ||
aa62e90f JY |
374 | return 0; |
375 | } | |
376 | ||
3ad2d861 | 377 | static int gpmc_onenand_setup(void __iomem *onenand_base, int *freq_ptr) |
aa62e90f JY |
378 | { |
379 | struct device *dev = &gpmc_onenand_device.dev; | |
46376884 AM |
380 | unsigned l = ONENAND_SYNC_READ | ONENAND_SYNC_READWRITE; |
381 | int ret; | |
aa62e90f | 382 | |
46376884 AM |
383 | ret = omap2_onenand_setup_async(onenand_base); |
384 | if (ret) { | |
385 | dev_err(dev, "unable to set to async mode\n"); | |
386 | return ret; | |
aa62e90f JY |
387 | } |
388 | ||
46376884 AM |
389 | if (!(gpmc_onenand_data->flags & l)) |
390 | return 0; | |
391 | ||
392 | ret = omap2_onenand_setup_sync(onenand_base, freq_ptr); | |
393 | if (ret) | |
394 | dev_err(dev, "unable to set to sync mode\n"); | |
395 | return ret; | |
aa62e90f JY |
396 | } |
397 | ||
398 | void __init gpmc_onenand_init(struct omap_onenand_platform_data *_onenand_data) | |
399 | { | |
681988ba AM |
400 | int err; |
401 | ||
aa62e90f JY |
402 | gpmc_onenand_data = _onenand_data; |
403 | gpmc_onenand_data->onenand_setup = gpmc_onenand_setup; | |
404 | gpmc_onenand_device.dev.platform_data = gpmc_onenand_data; | |
405 | ||
406 | if (cpu_is_omap24xx() && | |
407 | (gpmc_onenand_data->flags & ONENAND_SYNC_READWRITE)) { | |
408 | printk(KERN_ERR "Onenand using only SYNC_READ on 24xx\n"); | |
409 | gpmc_onenand_data->flags &= ~ONENAND_SYNC_READWRITE; | |
410 | gpmc_onenand_data->flags |= ONENAND_SYNC_READ; | |
411 | } | |
412 | ||
eb77b6a7 AM |
413 | if (cpu_is_omap34xx()) |
414 | gpmc_onenand_data->flags |= ONENAND_IN_OMAP34XX; | |
415 | else | |
416 | gpmc_onenand_data->flags &= ~ONENAND_IN_OMAP34XX; | |
417 | ||
681988ba AM |
418 | err = gpmc_cs_request(gpmc_onenand_data->cs, ONENAND_IO_SIZE, |
419 | (unsigned long *)&gpmc_onenand_resource.start); | |
420 | if (err < 0) { | |
421 | pr_err("%s: Cannot request GPMC CS\n", __func__); | |
422 | return; | |
423 | } | |
424 | ||
425 | gpmc_onenand_resource.end = gpmc_onenand_resource.start + | |
426 | ONENAND_IO_SIZE - 1; | |
427 | ||
aa62e90f | 428 | if (platform_device_register(&gpmc_onenand_device) < 0) { |
681988ba AM |
429 | pr_err("%s: Unable to register OneNAND device\n", __func__); |
430 | gpmc_cs_free(gpmc_onenand_data->cs); | |
aa62e90f JY |
431 | return; |
432 | } | |
433 | } |