diff options
Diffstat (limited to 'drivers/mtd/maps/tegra_nor.c')
-rw-r--r-- | drivers/mtd/maps/tegra_nor.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/drivers/mtd/maps/tegra_nor.c b/drivers/mtd/maps/tegra_nor.c new file mode 100644 index 00000000000..b455fd5e1c0 --- /dev/null +++ b/drivers/mtd/maps/tegra_nor.c | |||
@@ -0,0 +1,483 @@ | |||
1 | /* | ||
2 | * drivers/mtd/maps/tegra_nor.c | ||
3 | * | ||
4 | * MTD mapping driver for the internal SNOR controller in Tegra SoCs | ||
5 | * | ||
6 | * Copyright (C) 2009 - 2012 NVIDIA Corporation | ||
7 | * | ||
8 | * Author: | ||
9 | * Raghavendra VK <rvk@nvidia.com> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | * | ||
16 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
18 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
19 | * more details. | ||
20 | * | ||
21 | * You should have received a copy of the GNU General Public License along | ||
22 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
23 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
24 | */ | ||
25 | |||
26 | #include <linux/platform_device.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/types.h> | ||
29 | #include <linux/kernel.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/ioport.h> | ||
32 | #include <linux/slab.h> | ||
33 | #include <linux/interrupt.h> | ||
34 | #include <linux/irq.h> | ||
35 | #include <linux/mutex.h> | ||
36 | #include <linux/mtd/mtd.h> | ||
37 | #include <linux/mtd/map.h> | ||
38 | #include <linux/mtd/partitions.h> | ||
39 | #include <linux/dma-mapping.h> | ||
40 | #include <linux/proc_fs.h> | ||
41 | #include <linux/io.h> | ||
42 | #include <linux/uaccess.h> | ||
43 | #include <linux/clk.h> | ||
44 | #include <linux/platform_data/tegra_nor.h> | ||
45 | #include <asm/cacheflush.h> | ||
46 | |||
47 | #define __BITMASK0(len) (BIT(len) - 1) | ||
48 | #define REG_FIELD(val, start, len) (((val) & __BITMASK0(len)) << (start)) | ||
49 | #define REG_GET_FIELD(val, start, len) (((val) >> (start)) & __BITMASK0(len)) | ||
50 | |||
51 | /* tegra gmi registers... */ | ||
52 | #define TEGRA_SNOR_CONFIG_REG 0x00 | ||
53 | #define TEGRA_SNOR_NOR_ADDR_PTR_REG 0x08 | ||
54 | #define TEGRA_SNOR_AHB_ADDR_PTR_REG 0x0C | ||
55 | #define TEGRA_SNOR_TIMING0_REG 0x10 | ||
56 | #define TEGRA_SNOR_TIMING1_REG 0x14 | ||
57 | #define TEGRA_SNOR_DMA_CFG_REG 0x20 | ||
58 | |||
59 | /* config register */ | ||
60 | #define TEGRA_SNOR_CONFIG_GO BIT(31) | ||
61 | #define TEGRA_SNOR_CONFIG_WORDWIDE BIT(30) | ||
62 | #define TEGRA_SNOR_CONFIG_DEVICE_TYPE BIT(29) | ||
63 | #define TEGRA_SNOR_CONFIG_MUX_MODE BIT(28) | ||
64 | #define TEGRA_SNOR_CONFIG_BURST_LEN(val) REG_FIELD((val), 26, 2) | ||
65 | #define TEGRA_SNOR_CONFIG_RDY_ACTIVE BIT(24) | ||
66 | #define TEGRA_SNOR_CONFIG_RDY_POLARITY BIT(23) | ||
67 | #define TEGRA_SNOR_CONFIG_ADV_POLARITY BIT(22) | ||
68 | #define TEGRA_SNOR_CONFIG_OE_WE_POLARITY BIT(21) | ||
69 | #define TEGRA_SNOR_CONFIG_CS_POLARITY BIT(20) | ||
70 | #define TEGRA_SNOR_CONFIG_NOR_DPD BIT(19) | ||
71 | #define TEGRA_SNOR_CONFIG_WP BIT(15) | ||
72 | #define TEGRA_SNOR_CONFIG_PAGE_SZ(val) REG_FIELD((val), 8, 2) | ||
73 | #define TEGRA_SNOR_CONFIG_MST_ENB BIT(7) | ||
74 | #define TEGRA_SNOR_CONFIG_SNOR_CS(val) REG_FIELD((val), 4, 2) | ||
75 | #define TEGRA_SNOR_CONFIG_CE_LAST REG_FIELD(3) | ||
76 | #define TEGRA_SNOR_CONFIG_CE_FIRST REG_FIELD(2) | ||
77 | #define TEGRA_SNOR_CONFIG_DEVICE_MODE(val) REG_FIELD((val), 0, 2) | ||
78 | |||
79 | /* dma config register */ | ||
80 | #define TEGRA_SNOR_DMA_CFG_GO BIT(31) | ||
81 | #define TEGRA_SNOR_DMA_CFG_BSY BIT(30) | ||
82 | #define TEGRA_SNOR_DMA_CFG_DIR BIT(29) | ||
83 | #define TEGRA_SNOR_DMA_CFG_INT_ENB BIT(28) | ||
84 | #define TEGRA_SNOR_DMA_CFG_INT_STA BIT(27) | ||
85 | #define TEGRA_SNOR_DMA_CFG_BRST_SZ(val) REG_FIELD((val), 24, 3) | ||
86 | #define TEGRA_SNOR_DMA_CFG_WRD_CNT(val) REG_FIELD((val), 2, 14) | ||
87 | |||
88 | /* timing 0 register */ | ||
89 | #define TEGRA_SNOR_TIMING0_PG_RDY(val) REG_FIELD((val), 28, 4) | ||
90 | #define TEGRA_SNOR_TIMING0_PG_SEQ(val) REG_FIELD((val), 20, 4) | ||
91 | #define TEGRA_SNOR_TIMING0_MUX(val) REG_FIELD((val), 12, 4) | ||
92 | #define TEGRA_SNOR_TIMING0_HOLD(val) REG_FIELD((val), 8, 4) | ||
93 | #define TEGRA_SNOR_TIMING0_ADV(val) REG_FIELD((val), 4, 4) | ||
94 | #define TEGRA_SNOR_TIMING0_CE(val) REG_FIELD((val), 0, 4) | ||
95 | |||
96 | /* timing 1 register */ | ||
97 | #define TEGRA_SNOR_TIMING1_WE(val) REG_FIELD((val), 16, 8) | ||
98 | #define TEGRA_SNOR_TIMING1_OE(val) REG_FIELD((val), 8, 8) | ||
99 | #define TEGRA_SNOR_TIMING1_WAIT(val) REG_FIELD((val), 0, 8) | ||
100 | |||
101 | /* SNOR DMA supports 2^14 AHB (32-bit words) | ||
102 | * Maximum data in one transfer = 2^16 bytes | ||
103 | */ | ||
104 | #define TEGRA_SNOR_DMA_LIMIT 0x10000 | ||
105 | #define TEGRA_SNOR_DMA_LIMIT_WORDS (TEGRA_SNOR_DMA_LIMIT >> 2) | ||
106 | |||
107 | /* Even if BW is 1 MB/s, maximum time to | ||
108 | * transfer SNOR_DMA_LIMIT bytes is 66 ms | ||
109 | */ | ||
110 | #define TEGRA_SNOR_DMA_TIMEOUT_MS 67 | ||
111 | |||
112 | struct tegra_nor_info { | ||
113 | struct tegra_nor_platform_data *plat; | ||
114 | struct device *dev; | ||
115 | struct clk *clk; | ||
116 | struct mtd_partition *parts; | ||
117 | struct mtd_info *mtd; | ||
118 | struct map_info map; | ||
119 | struct completion dma_complete; | ||
120 | void __iomem *base; | ||
121 | void *dma_virt_buffer; | ||
122 | dma_addr_t dma_phys_buffer; | ||
123 | u32 init_config; | ||
124 | u32 timing0_default, timing1_default; | ||
125 | u32 timing0_read, timing1_read; | ||
126 | }; | ||
127 | |||
128 | static inline unsigned long snor_tegra_readl(struct tegra_nor_info *tnor, | ||
129 | unsigned long reg) | ||
130 | { | ||
131 | return readl(tnor->base + reg); | ||
132 | } | ||
133 | |||
134 | static inline void snor_tegra_writel(struct tegra_nor_info *tnor, | ||
135 | unsigned long val, unsigned long reg) | ||
136 | { | ||
137 | writel(val, tnor->base + reg); | ||
138 | } | ||
139 | |||
140 | #define DRV_NAME "tegra-nor" | ||
141 | |||
142 | static const char * const part_probes[] = { "cmdlinepart", NULL }; | ||
143 | |||
144 | static int wait_for_dma_completion(struct tegra_nor_info *info) | ||
145 | { | ||
146 | unsigned long dma_timeout; | ||
147 | int ret; | ||
148 | |||
149 | dma_timeout = msecs_to_jiffies(TEGRA_SNOR_DMA_TIMEOUT_MS); | ||
150 | ret = wait_for_completion_timeout(&info->dma_complete, dma_timeout); | ||
151 | return ret ? 0 : -ETIMEDOUT; | ||
152 | } | ||
153 | |||
154 | static void tegra_flash_dma(struct map_info *map, | ||
155 | void *to, unsigned long from, ssize_t len) | ||
156 | { | ||
157 | u32 snor_config, dma_config = 0; | ||
158 | int dma_transfer_count = 0, word32_count = 0; | ||
159 | u32 nor_address, current_transfer = 0; | ||
160 | u32 copy_to = (u32)to; | ||
161 | struct tegra_nor_info *c = | ||
162 | container_of(map, struct tegra_nor_info, map); | ||
163 | unsigned int bytes_remaining = len; | ||
164 | |||
165 | snor_config = c->init_config; | ||
166 | snor_tegra_writel(c, c->timing0_read, TEGRA_SNOR_TIMING0_REG); | ||
167 | snor_tegra_writel(c, c->timing1_read, TEGRA_SNOR_TIMING1_REG); | ||
168 | |||
169 | if (len > 32) { | ||
170 | word32_count = len >> 2; | ||
171 | bytes_remaining = len & 0x00000003; | ||
172 | /* | ||
173 | * The parameters can be setup in any order since we write to | ||
174 | * controller register only after all parameters are set. | ||
175 | */ | ||
176 | /* SNOR CONFIGURATION SETUP */ | ||
177 | snor_config |= TEGRA_SNOR_CONFIG_DEVICE_MODE(1); | ||
178 | /* 8 word page */ | ||
179 | snor_config |= TEGRA_SNOR_CONFIG_PAGE_SZ(2); | ||
180 | snor_config |= TEGRA_SNOR_CONFIG_MST_ENB; | ||
181 | /* SNOR DMA CONFIGURATION SETUP */ | ||
182 | /* NOR -> AHB */ | ||
183 | dma_config &= ~TEGRA_SNOR_DMA_CFG_DIR; | ||
184 | /* One word burst */ | ||
185 | dma_config |= TEGRA_SNOR_DMA_CFG_BRST_SZ(4); | ||
186 | |||
187 | for (nor_address = (unsigned int)(map->phys + from); | ||
188 | word32_count > 0; | ||
189 | word32_count -= current_transfer, | ||
190 | dma_transfer_count += current_transfer, | ||
191 | nor_address += (current_transfer * 4), | ||
192 | copy_to += (current_transfer * 4)) { | ||
193 | |||
194 | current_transfer = | ||
195 | (word32_count > TEGRA_SNOR_DMA_LIMIT_WORDS) | ||
196 | ? (TEGRA_SNOR_DMA_LIMIT_WORDS) : word32_count; | ||
197 | /* Start NOR operation */ | ||
198 | snor_config |= TEGRA_SNOR_CONFIG_GO; | ||
199 | dma_config |= TEGRA_SNOR_DMA_CFG_GO; | ||
200 | /* Enable interrupt before every transaction since the | ||
201 | * interrupt handler disables it */ | ||
202 | dma_config |= TEGRA_SNOR_DMA_CFG_INT_ENB; | ||
203 | /* Num of AHB (32-bit) words to transferred minus 1 */ | ||
204 | dma_config |= | ||
205 | TEGRA_SNOR_DMA_CFG_WRD_CNT(current_transfer - 1); | ||
206 | snor_tegra_writel(c, c->dma_phys_buffer, | ||
207 | TEGRA_SNOR_AHB_ADDR_PTR_REG); | ||
208 | snor_tegra_writel(c, nor_address, | ||
209 | TEGRA_SNOR_NOR_ADDR_PTR_REG); | ||
210 | snor_tegra_writel(c, snor_config, | ||
211 | TEGRA_SNOR_CONFIG_REG); | ||
212 | snor_tegra_writel(c, dma_config, | ||
213 | TEGRA_SNOR_DMA_CFG_REG); | ||
214 | if (wait_for_dma_completion(c)) { | ||
215 | dev_err(c->dev, "timout waiting for DMA\n"); | ||
216 | /* Transfer the remaining words by memcpy */ | ||
217 | bytes_remaining += (word32_count << 2); | ||
218 | break; | ||
219 | } | ||
220 | memcpy((char *)(copy_to), (char *)(c->dma_virt_buffer), | ||
221 | (current_transfer << 2)); | ||
222 | |||
223 | } | ||
224 | } | ||
225 | /* Put the controller back into slave mode. */ | ||
226 | snor_config = snor_tegra_readl(c, TEGRA_SNOR_CONFIG_REG); | ||
227 | snor_config &= ~TEGRA_SNOR_CONFIG_MST_ENB; | ||
228 | snor_config |= TEGRA_SNOR_CONFIG_DEVICE_MODE(0); | ||
229 | snor_tegra_writel(c, snor_config, TEGRA_SNOR_CONFIG_REG); | ||
230 | |||
231 | memcpy_fromio(((char *)to + (dma_transfer_count << 2)), | ||
232 | ((char *)(map->virt + from) + (dma_transfer_count << 2)), | ||
233 | bytes_remaining); | ||
234 | |||
235 | snor_tegra_writel(c, c->timing0_default, TEGRA_SNOR_TIMING0_REG); | ||
236 | snor_tegra_writel(c, c->timing1_default, TEGRA_SNOR_TIMING1_REG); | ||
237 | } | ||
238 | |||
239 | static irqreturn_t tegra_nor_isr(int flag, void *dev_id) | ||
240 | { | ||
241 | struct tegra_nor_info *info = (struct tegra_nor_info *)dev_id; | ||
242 | u32 dma_config = snor_tegra_readl(info, TEGRA_SNOR_DMA_CFG_REG); | ||
243 | if (dma_config & TEGRA_SNOR_DMA_CFG_INT_STA) { | ||
244 | /* Disable interrupts. WAR for BUG:821560 */ | ||
245 | dma_config &= ~TEGRA_SNOR_DMA_CFG_INT_ENB; | ||
246 | snor_tegra_writel(info, dma_config, TEGRA_SNOR_DMA_CFG_REG); | ||
247 | complete(&info->dma_complete); | ||
248 | } else { | ||
249 | pr_err("%s: Spurious interrupt\n", __func__); | ||
250 | } | ||
251 | return IRQ_HANDLED; | ||
252 | } | ||
253 | |||
254 | static int tegra_snor_controller_init(struct tegra_nor_info *info) | ||
255 | { | ||
256 | struct tegra_nor_chip_parms *chip_parm = &info->plat->chip_parms; | ||
257 | u32 width = info->plat->flash.width; | ||
258 | u32 config = 0; | ||
259 | |||
260 | config |= TEGRA_SNOR_CONFIG_DEVICE_MODE(0); | ||
261 | config |= TEGRA_SNOR_CONFIG_SNOR_CS(0); | ||
262 | config &= ~TEGRA_SNOR_CONFIG_DEVICE_TYPE; /* Select NOR */ | ||
263 | config |= TEGRA_SNOR_CONFIG_WP; /* Enable writes */ | ||
264 | switch (width) { | ||
265 | case 2: | ||
266 | config &= ~TEGRA_SNOR_CONFIG_WORDWIDE; /* 16 bit */ | ||
267 | break; | ||
268 | case 4: | ||
269 | config |= TEGRA_SNOR_CONFIG_WORDWIDE; /* 32 bit */ | ||
270 | break; | ||
271 | default: | ||
272 | return -EINVAL; | ||
273 | } | ||
274 | config |= TEGRA_SNOR_CONFIG_BURST_LEN(0); | ||
275 | config &= ~TEGRA_SNOR_CONFIG_MUX_MODE; | ||
276 | snor_tegra_writel(info, config, TEGRA_SNOR_CONFIG_REG); | ||
277 | info->init_config = config; | ||
278 | |||
279 | info->timing0_default = chip_parm->timing_default.timing0; | ||
280 | info->timing0_read = chip_parm->timing_read.timing0; | ||
281 | info->timing1_default = chip_parm->timing_default.timing1; | ||
282 | info->timing1_read = chip_parm->timing_read.timing0; | ||
283 | |||
284 | snor_tegra_writel(info, info->timing1_default, TEGRA_SNOR_TIMING1_REG); | ||
285 | snor_tegra_writel(info, info->timing0_default, TEGRA_SNOR_TIMING0_REG); | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | static int tegra_nor_probe(struct platform_device *pdev) | ||
290 | { | ||
291 | int err = 0; | ||
292 | struct tegra_nor_platform_data *plat = pdev->dev.platform_data; | ||
293 | struct tegra_nor_info *info = NULL; | ||
294 | struct device *dev = &pdev->dev; | ||
295 | struct resource *res; | ||
296 | int irq; | ||
297 | |||
298 | if (!plat) { | ||
299 | pr_err("%s: no platform device info\n", __func__); | ||
300 | err = -EINVAL; | ||
301 | goto fail; | ||
302 | } | ||
303 | |||
304 | info = devm_kzalloc(dev, sizeof(struct tegra_nor_info), | ||
305 | GFP_KERNEL); | ||
306 | if (!info) { | ||
307 | err = -ENOMEM; | ||
308 | goto fail; | ||
309 | } | ||
310 | |||
311 | /* Get NOR controller & map the same */ | ||
312 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
313 | if (!res) { | ||
314 | dev_err(dev, "no mem resource?\n"); | ||
315 | err = -ENODEV; | ||
316 | goto fail; | ||
317 | } | ||
318 | |||
319 | if (!devm_request_mem_region(dev, res->start, resource_size(res), | ||
320 | dev_name(&pdev->dev))) { | ||
321 | dev_err(dev, "NOR region already claimed\n"); | ||
322 | err = -EBUSY; | ||
323 | goto fail; | ||
324 | } | ||
325 | |||
326 | info->base = devm_ioremap(dev, res->start, resource_size(res)); | ||
327 | if (!info->base) { | ||
328 | dev_err(dev, "Can't ioremap NOR region\n"); | ||
329 | err = -ENOMEM; | ||
330 | goto fail; | ||
331 | } | ||
332 | |||
333 | /* Get NOR flash aperture & map the same */ | ||
334 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | ||
335 | if (!res) { | ||
336 | dev_err(dev, "no mem resource?\n"); | ||
337 | err = -ENODEV; | ||
338 | goto fail; | ||
339 | } | ||
340 | |||
341 | if (!devm_request_mem_region(dev, res->start, resource_size(res), | ||
342 | dev_name(dev))) { | ||
343 | dev_err(dev, "NOR region already claimed\n"); | ||
344 | err = -EBUSY; | ||
345 | goto fail; | ||
346 | } | ||
347 | |||
348 | info->map.virt = devm_ioremap(dev, res->start, | ||
349 | resource_size(res)); | ||
350 | if (!info->map.virt) { | ||
351 | dev_err(dev, "Can't ioremap NOR region\n"); | ||
352 | err = -ENOMEM; | ||
353 | goto fail; | ||
354 | } | ||
355 | |||
356 | info->plat = plat; | ||
357 | info->dev = dev; | ||
358 | info->map.bankwidth = plat->flash.width; | ||
359 | info->map.name = dev_name(dev); | ||
360 | info->map.phys = res->start; | ||
361 | info->map.size = resource_size(res); | ||
362 | |||
363 | info->clk = clk_get(dev, NULL); | ||
364 | if (IS_ERR(info->clk)) { | ||
365 | err = PTR_ERR(info->clk); | ||
366 | goto fail; | ||
367 | } | ||
368 | |||
369 | err = clk_enable(info->clk); | ||
370 | if (err != 0) | ||
371 | goto out_clk_put; | ||
372 | |||
373 | simple_map_init(&info->map); | ||
374 | info->map.copy_from = tegra_flash_dma; | ||
375 | |||
376 | /* Intialise the SNOR controller before probe */ | ||
377 | err = tegra_snor_controller_init(info); | ||
378 | if (err) { | ||
379 | dev_err(dev, "Error initializing controller\n"); | ||
380 | goto out_clk_disable; | ||
381 | } | ||
382 | |||
383 | init_completion(&info->dma_complete); | ||
384 | |||
385 | irq = platform_get_irq(pdev, 0); | ||
386 | if (!irq) { | ||
387 | dev_err(dev, "no irq resource?\n"); | ||
388 | err = -ENODEV; | ||
389 | goto out_clk_disable; | ||
390 | } | ||
391 | |||
392 | /* Register SNOR DMA completion interrupt */ | ||
393 | err = devm_request_irq(dev, irq, tegra_nor_isr, IRQF_DISABLED, | ||
394 | dev_name(dev), info); | ||
395 | if (err) { | ||
396 | dev_err(dev, "Failed to request irq %i\n", irq); | ||
397 | goto out_clk_disable; | ||
398 | } | ||
399 | info->dma_virt_buffer = dma_alloc_coherent(dev, | ||
400 | TEGRA_SNOR_DMA_LIMIT, | ||
401 | &info->dma_phys_buffer, | ||
402 | GFP_KERNEL); | ||
403 | if (info->dma_virt_buffer == NULL) { | ||
404 | dev_err(&pdev->dev, "Could not allocate buffer for DMA"); | ||
405 | err = -ENOMEM; | ||
406 | goto out_clk_disable; | ||
407 | } | ||
408 | |||
409 | info->mtd = do_map_probe(plat->flash.map_name, &info->map); | ||
410 | if (!info->mtd) { | ||
411 | err = -EIO; | ||
412 | goto out_dma_free_coherent; | ||
413 | } | ||
414 | info->mtd->owner = THIS_MODULE; | ||
415 | info->parts = NULL; | ||
416 | |||
417 | platform_set_drvdata(pdev, info); | ||
418 | err = parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); | ||
419 | if (err > 0) | ||
420 | err = mtd_device_register(info->mtd, info->parts, err); | ||
421 | else if (err <= 0 && plat->flash.parts) | ||
422 | err = | ||
423 | mtd_device_register(info->mtd, plat->flash.parts, | ||
424 | plat->flash.nr_parts); | ||
425 | else | ||
426 | mtd_device_register(info->mtd, NULL, 0); | ||
427 | |||
428 | return 0; | ||
429 | |||
430 | out_dma_free_coherent: | ||
431 | dma_free_coherent(dev, TEGRA_SNOR_DMA_LIMIT, | ||
432 | info->dma_virt_buffer, info->dma_phys_buffer); | ||
433 | out_clk_disable: | ||
434 | clk_disable(info->clk); | ||
435 | out_clk_put: | ||
436 | clk_put(info->clk); | ||
437 | fail: | ||
438 | pr_err("Tegra NOR probe failed\n"); | ||
439 | return err; | ||
440 | } | ||
441 | |||
442 | static int tegra_nor_remove(struct platform_device *pdev) | ||
443 | { | ||
444 | struct tegra_nor_info *info = platform_get_drvdata(pdev); | ||
445 | |||
446 | mtd_device_unregister(info->mtd); | ||
447 | if (info->parts) | ||
448 | kfree(info->parts); | ||
449 | dma_free_coherent(&pdev->dev, TEGRA_SNOR_DMA_LIMIT, | ||
450 | info->dma_virt_buffer, info->dma_phys_buffer); | ||
451 | map_destroy(info->mtd); | ||
452 | clk_disable(info->clk); | ||
453 | clk_put(info->clk); | ||
454 | |||
455 | return 0; | ||
456 | } | ||
457 | |||
458 | static struct platform_driver __refdata tegra_nor_driver = { | ||
459 | .probe = tegra_nor_probe, | ||
460 | .remove = __devexit_p(tegra_nor_remove), | ||
461 | .driver = { | ||
462 | .name = DRV_NAME, | ||
463 | .owner = THIS_MODULE, | ||
464 | }, | ||
465 | }; | ||
466 | |||
467 | static int __init tegra_nor_init(void) | ||
468 | { | ||
469 | return platform_driver_register(&tegra_nor_driver); | ||
470 | } | ||
471 | |||
472 | static void __exit tegra_nor_exit(void) | ||
473 | { | ||
474 | platform_driver_unregister(&tegra_nor_driver); | ||
475 | } | ||
476 | |||
477 | module_init(tegra_nor_init); | ||
478 | module_exit(tegra_nor_exit); | ||
479 | |||
480 | MODULE_AUTHOR("Raghavendra VK <rvk@nvidia.com>"); | ||
481 | MODULE_DESCRIPTION("NOR Flash mapping driver for NVIDIA Tegra based boards"); | ||
482 | MODULE_LICENSE("GPL"); | ||
483 | MODULE_ALIAS("platform:" DRV_NAME); | ||