OpenWrt – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Copyright (c) 2018 Jianhui Zhao <jianhuizhao329@gmail.com> |
||
3 | * |
||
4 | * This program is free software; you can redistribute it and/or modify |
||
5 | * it under the terms of the GNU General Public License version 2 as |
||
6 | * published by the Free Software Foundation. |
||
7 | */ |
||
8 | |||
9 | #include <linux/module.h> |
||
10 | #include <linux/delay.h> |
||
11 | #include <linux/types.h> |
||
12 | #include <linux/init.h> |
||
13 | #include <linux/kernel.h> |
||
14 | #include <linux/string.h> |
||
15 | #include <linux/ioport.h> |
||
16 | #include <linux/of_platform.h> |
||
17 | #include <linux/delay.h> |
||
18 | #include <linux/err.h> |
||
19 | #include <linux/clk.h> |
||
20 | #include <linux/jiffies.h> |
||
21 | #include <linux/sched.h> |
||
22 | #include <linux/slab.h> |
||
23 | #include <linux/io.h> |
||
24 | |||
25 | #include <linux/mtd/mtd.h> |
||
26 | #include <linux/mtd/rawnand.h> |
||
27 | #include <linux/mtd/nand_ecc.h> |
||
28 | #include <linux/mtd/partitions.h> |
||
29 | |||
30 | #define S5P_NFCONF 0x00 |
||
31 | #define S5P_NFCONT 0x04 |
||
32 | #define S5P_NFCMD 0x08 |
||
33 | #define S5P_NFADDR 0x0c |
||
34 | #define S5P_NFDATA 0x10 |
||
35 | #define S5P_NFMECCDATA0 0x14 |
||
36 | #define S5P_NFMECCDATA1 0x18 |
||
37 | #define S5P_NFSECCDATA 0x1c |
||
38 | #define S5P_NFSBLK 0x20 |
||
39 | #define S5P_NFEBLK 0x24 |
||
40 | #define S5P_NFSTAT 0x28 |
||
41 | #define S5P_NFMECCERR0 0x2c |
||
42 | #define S5P_NFMECCERR1 0x30 |
||
43 | #define S5P_NFMECC0 0x34 |
||
44 | #define S5P_NFMECC1 0x38 |
||
45 | #define S5P_NFSECC 0x3c |
||
46 | #define S5P_NFMLCBITPT 0x40 |
||
47 | #define S5P_NF8ECCERR0 0x44 |
||
48 | #define S5P_NF8ECCERR1 0x48 |
||
49 | #define S5P_NF8ECCERR2 0x4C |
||
50 | #define S5P_NFM8ECC0 0x50 |
||
51 | #define S5P_NFM8ECC1 0x54 |
||
52 | #define S5P_NFM8ECC2 0x58 |
||
53 | #define S5P_NFM8ECC3 0x5C |
||
54 | #define S5P_NFMLC8BITPT0 0x60 |
||
55 | #define S5P_NFMLC8BITPT1 0x64 |
||
56 | |||
57 | #define S5P_NFECCCONF 0x00 |
||
58 | #define S5P_NFECCCONT 0x20 |
||
59 | #define S5P_NFECCSTAT 0x30 |
||
60 | #define S5P_NFECCSECSTAT 0x40 |
||
61 | #define S5P_NFECCPRGECC 0x90 |
||
62 | #define S5P_NFECCERL 0xC0 |
||
63 | #define S5P_NFECCERP 0xF0 |
||
64 | |||
65 | #define S5P_NFCONF_NANDBOOT (1 << 31) |
||
66 | #define S5P_NFCONF_ECCCLKCON (1 << 30) |
||
67 | #define S5P_NFCONF_ECC_MLC (1 << 24) |
||
68 | #define S5P_NFCONF_ECC_1BIT (0 << 23) |
||
69 | #define S5P_NFCONF_ECC_4BIT (2 << 23) |
||
70 | #define S5P_NFCONF_ECC_8BIT (1 << 23) |
||
71 | #define S5P_NFCONF_TACLS(x) ((x) << 12) |
||
72 | #define S5P_NFCONF_TWRPH0(x) ((x) << 8) |
||
73 | #define S5P_NFCONF_TWRPH1(x) ((x) << 4) |
||
74 | #define S5P_NFCONF_MLC (1 << 3) |
||
75 | #define S5P_NFCONF_PAGESIZE (1 << 2) |
||
76 | #define S5P_NFCONF_ADDRCYCLE (1 << 1) |
||
77 | #define S5P_NFCONF_BUSWIDTH (1 << 0) |
||
78 | |||
79 | #define S5P_NFCONT_ECC_ENC (1 << 18) |
||
80 | #define S5P_NFCONT_LOCKTGHT (1 << 17) |
||
81 | #define S5P_NFCONT_LOCKSOFT (1 << 16) |
||
82 | #define S5P_NFCONT_MECCLOCK (1 << 7) |
||
83 | #define S5P_NFCONT_SECCLOCK (1 << 6) |
||
84 | #define S5P_NFCONT_INITMECC (1 << 5) |
||
85 | #define S5P_NFCONT_INITSECC (1 << 4) |
||
86 | #define S5P_NFCONT_nFCE1 (1 << 2) |
||
87 | #define S5P_NFCONT_nFCE0 (1 << 1) |
||
88 | #define S5P_NFCONT_MODE (1 << 0) |
||
89 | |||
90 | #define S5P_NFSTAT_READY (1 << 0) |
||
91 | |||
92 | #define S5P_NFECCCONT_MECCRESET (1 << 0) |
||
93 | #define S5P_NFECCCONT_MECCINIT (1 << 2) |
||
94 | #define S5P_NFECCCONT_ECCDIRWR (1 << 16) |
||
95 | |||
96 | #define S5P_NFECCSTAT_ECCBUSY (1 << 31) |
||
97 | |||
98 | enum s5p_cpu_type { |
||
99 | TYPE_S5PV210, |
||
100 | }; |
||
101 | |||
102 | struct s5p_nand_host { |
||
103 | struct nand_chip nand_chip; |
||
104 | void __iomem *nf_base; |
||
105 | void __iomem *ecc_base; |
||
106 | struct clk *clk[2]; |
||
107 | enum s5p_cpu_type cpu_type; |
||
108 | }; |
||
109 | |||
110 | /* |
||
111 | * See "S5PV210 iROM Application Note" for recommended ECC layout |
||
112 | * ECC layout for 8-bit ECC (13 bytes/page) |
||
113 | * Compatible with bl0 bootloader, see iROM appnote |
||
114 | */ |
||
115 | /* new oob placement block for use with hardware ecc generation |
||
116 | */ |
||
117 | static int s5pcxx_ooblayout_ecc(struct mtd_info *mtd, int section, |
||
118 | struct mtd_oob_region *oobregion) |
||
119 | { |
||
120 | if (section) |
||
121 | return -ERANGE; |
||
122 | |||
123 | oobregion->offset = 12; |
||
124 | oobregion->length = 52; |
||
125 | |||
126 | return 0; |
||
127 | } |
||
128 | |||
129 | static int s5pcxx_ooblayout_free(struct mtd_info *mtd, int section, |
||
130 | struct mtd_oob_region *oobregion) |
||
131 | { |
||
132 | if (section) |
||
133 | return -ERANGE; |
||
134 | |||
135 | oobregion->offset = 2; |
||
136 | oobregion->length = 10; |
||
137 | |||
138 | return 0; |
||
139 | } |
||
140 | |||
141 | static const struct mtd_ooblayout_ops s5pcxx_ooblayout_ops = { |
||
142 | .ecc = s5pcxx_ooblayout_ecc, |
||
143 | .free = s5pcxx_ooblayout_free, |
||
144 | }; |
||
145 | |||
146 | static inline void rwl(void *reg, uint32_t rst, uint32_t set) |
||
147 | { |
||
148 | uint32_t r; |
||
149 | r = readl(reg); |
||
150 | r &= ~rst; |
||
151 | r |= set; |
||
152 | writel(r, reg); |
||
153 | } |
||
154 | |||
155 | /* |
||
156 | * Hardware specific access to control-lines function |
||
157 | */ |
||
158 | static void s5p_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) |
||
159 | { |
||
160 | struct nand_chip *nand_chip = mtd->priv; |
||
161 | struct s5p_nand_host *host = nand_chip->priv; |
||
162 | |||
163 | if (dat == NAND_CMD_NONE) |
||
164 | return; |
||
165 | |||
166 | if (ctrl & NAND_CLE) |
||
167 | writeb(dat, host->nf_base + S5P_NFCMD); |
||
168 | else |
||
169 | writeb(dat, host->nf_base + S5P_NFADDR); |
||
170 | } |
||
171 | |||
172 | /* |
||
173 | * Function for checking device ready pin |
||
174 | */ |
||
175 | static int s5p_nand_device_ready(struct mtd_info *mtd) |
||
176 | { |
||
177 | struct nand_chip *nand_chip = mtd->priv; |
||
178 | struct s5p_nand_host *host = nand_chip->priv; |
||
179 | |||
180 | /* it's to check the RnB nand signal bit and |
||
181 | * return to device ready condition in nand_base.c |
||
182 | */ |
||
183 | return readl(host->nf_base + S5P_NFSTAT) & S5P_NFSTAT_READY; |
||
184 | } |
||
185 | |||
186 | static void s3_nand_select_chip(struct mtd_info *mtd, int chip) |
||
187 | { |
||
188 | struct nand_chip *nand_chip = mtd->priv; |
||
189 | struct s5p_nand_host *host = nand_chip->priv; |
||
190 | u32 value = readl(host->nf_base + S5P_NFCONT); |
||
191 | |||
192 | if (chip == -1) |
||
193 | value |= S5P_NFCONT_nFCE0; /* deselect */ |
||
194 | else |
||
195 | value &= ~S5P_NFCONT_nFCE0; /* select */ |
||
196 | |||
197 | writel(value, host->nf_base + S5P_NFCONT); |
||
198 | } |
||
199 | |||
200 | static void s5pcxx_nand_enable_hwecc(struct mtd_info *mtd, int mode) |
||
201 | { |
||
202 | struct nand_chip *chip = mtd->priv; |
||
203 | struct s5p_nand_host *host = chip->priv; |
||
204 | |||
205 | uint32_t reg; |
||
206 | |||
207 | /* Set ECC mode */ |
||
208 | reg = 3; /* 8-bit */ |
||
209 | reg |= (chip->ecc.size - 1) << 16; |
||
210 | writel(reg, host->ecc_base + S5P_NFECCCONF); |
||
211 | |||
212 | /* Set ECC direction */ |
||
213 | rwl(host->ecc_base + S5P_NFECCCONT, S5P_NFECCCONT_ECCDIRWR, |
||
214 | (mode == NAND_ECC_WRITE) ? S5P_NFECCCONT_ECCDIRWR : 0); |
||
215 | |||
216 | /* Reset status bits */ |
||
217 | rwl(host->ecc_base + S5P_NFECCSTAT, 0, (1 << 24) | (1 << 25)); |
||
218 | |||
219 | /* Unlock ECC */ |
||
220 | rwl(host->nf_base + S5P_NFCONT, S5P_NFCONT_MECCLOCK, 0); |
||
221 | |||
222 | /* Initialize ECC */ |
||
223 | rwl(host->ecc_base +S5P_NFECCCONT, 0, S5P_NFECCCONT_MECCINIT); |
||
224 | } |
||
225 | |||
226 | static void readecc(void *eccbase, uint8_t *ecc_code, unsigned ecc_len) |
||
227 | { |
||
228 | uint32_t i, j, reg; |
||
229 | |||
230 | for (i = 0; i < ecc_len; i += 4) { |
||
231 | reg = readl(eccbase + i); |
||
232 | for (j = 0; (j < 4) && (i + j < ecc_len); ++j) { |
||
233 | ecc_code[i + j] = reg & 0xFF; |
||
234 | reg >>= 8; |
||
235 | } |
||
236 | } |
||
237 | } |
||
238 | |||
239 | static int s5pcxx_nand_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, uint8_t *ecc_code) |
||
240 | { |
||
241 | struct nand_chip *chip = mtd->priv; |
||
242 | struct s5p_nand_host *host = chip->priv; |
||
243 | |||
244 | /* Lock ECC */ |
||
245 | rwl(host->nf_base + S5P_NFCONT, 0, S5P_NFCONT_MECCLOCK); |
||
246 | |||
247 | if (ecc_code) /* NAND_ECC_WRITE */ { |
||
248 | /* ECC encoding is completed */ |
||
249 | while (!(readl(host->ecc_base + S5P_NFECCSTAT) & (1 << 25))); |
||
250 | readecc(host->ecc_base + S5P_NFECCPRGECC, ecc_code, chip->ecc.bytes); |
||
251 | } else { /* NAND_ECC_READ */ |
||
252 | /* ECC decoding is completed */ |
||
253 | while (!(readl(host->ecc_base + S5P_NFECCSTAT) & (1 << 24))); |
||
254 | } |
||
255 | |||
256 | return 0; |
||
257 | } |
||
258 | |||
259 | static int s5pcxx_nand_correct_data(struct mtd_info *mtd, u8 *dat, |
||
260 | u8 *read_ecc, u8 *calc_ecc) |
||
261 | { |
||
262 | int ret = 0; |
||
263 | u32 errNo; |
||
264 | u32 erl0, erl1, erl2, erl3, erp0, erp1; |
||
265 | struct nand_chip *chip = mtd->priv; |
||
266 | struct s5p_nand_host *host = chip->priv; |
||
267 | |||
268 | /* Wait until the 8-bit ECC decoding engine is Idle */ |
||
269 | while (readl(host->ecc_base + S5P_NFECCSTAT) & (1 << 31)); |
||
270 | |||
271 | errNo = readl(host->ecc_base + S5P_NFECCSECSTAT) & 0x1F; |
||
272 | erl0 = readl(host->ecc_base + S5P_NFECCERL); |
||
273 | erl1 = readl(host->ecc_base + S5P_NFECCERL + 0x04); |
||
274 | erl2 = readl(host->ecc_base + S5P_NFECCERL + 0x08); |
||
275 | erl3 = readl(host->ecc_base + S5P_NFECCERL + 0x0C); |
||
276 | |||
277 | erp0 = readl(host->ecc_base + S5P_NFECCERP); |
||
278 | erp1 = readl(host->ecc_base + S5P_NFECCERP + 0x04); |
||
279 | |||
280 | switch (errNo) { |
||
281 | case 8: |
||
282 | dat[(erl3 >> 16) & 0x3FF] ^= (erp1 >> 24) & 0xFF; |
||
283 | case 7: |
||
284 | dat[erl3 & 0x3FF] ^= (erp1 >> 16) & 0xFF; |
||
285 | case 6: |
||
286 | dat[(erl2 >> 16) & 0x3FF] ^= (erp1 >> 8) & 0xFF; |
||
287 | case 5: |
||
288 | dat[erl2 & 0x3FF] ^= erp1 & 0xFF; |
||
289 | case 4: |
||
290 | dat[(erl1 >> 16) & 0x3FF] ^= (erp0 >> 24) & 0xFF; |
||
291 | case 3: |
||
292 | dat[erl1 & 0x3FF] ^= (erp0 >> 16) & 0xFF; |
||
293 | case 2: |
||
294 | dat[(erl0 >> 16) & 0x3FF] ^= (erp0 >> 8) & 0xFF; |
||
295 | case 1: |
||
296 | dat[erl0 & 0x3FF] ^= erp0 & 0xFF; |
||
297 | case 0: |
||
298 | break; |
||
299 | default: |
||
300 | ret = -1; |
||
301 | printk("ECC uncorrectable error detected:%d\n", errNo); |
||
302 | break; |
||
303 | } |
||
304 | |||
305 | return ret; |
||
306 | } |
||
307 | |||
308 | static int s5pcxx_nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, |
||
309 | uint8_t *buf, int oob_required, int page) |
||
310 | { |
||
311 | struct mtd_oob_region oobregion = { }; |
||
312 | int i, eccsize = chip->ecc.size; |
||
313 | int eccbytes = chip->ecc.bytes; |
||
314 | int eccsteps = chip->ecc.steps; |
||
315 | uint8_t *oobecc; |
||
316 | int col, stat; |
||
317 | |||
318 | /* Read the OOB area first */ |
||
319 | chip->ecc.read_oob(mtd, chip, page); |
||
320 | mtd_ooblayout_ecc(mtd, 0, &oobregion); |
||
321 | oobecc = chip->oob_poi + oobregion.offset; |
||
322 | |||
323 | for (i = 0, col = 0; eccsteps; eccsteps--, i += eccbytes, buf += eccsize, col += eccsize) { |
||
324 | chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1); |
||
325 | chip->ecc.hwctl(mtd, NAND_ECC_READ); |
||
326 | chip->read_buf(mtd, buf, eccsize); |
||
327 | chip->write_buf(mtd, oobecc + i, eccbytes); |
||
328 | chip->ecc.calculate(mtd, NULL, NULL); |
||
329 | stat = chip->ecc.correct(mtd, buf, NULL, NULL); |
||
330 | if (stat < 0) |
||
331 | mtd->ecc_stats.failed++; |
||
332 | else |
||
333 | mtd->ecc_stats.corrected += stat; |
||
334 | } |
||
335 | return 0; |
||
336 | } |
||
337 | |||
338 | static void s5p_nand_inithw_later(struct mtd_info *mtd) |
||
339 | { |
||
340 | struct nand_chip *chip = mtd->priv; |
||
341 | struct s5p_nand_host *host = chip->priv; |
||
342 | u32 value; |
||
343 | |||
344 | value = readl(host->nf_base + S5P_NFCONF); |
||
345 | |||
346 | if (nand_is_slc(chip)) { |
||
347 | value &= ~S5P_NFCONF_MLC; |
||
348 | |||
349 | if (mtd->writesize == 512) { |
||
350 | value |= S5P_NFCONF_PAGESIZE; |
||
351 | |||
352 | } else { |
||
353 | value &= ~S5P_NFCONF_PAGESIZE; |
||
354 | } |
||
355 | } else { |
||
356 | value |= S5P_NFCONF_MLC; |
||
357 | |||
358 | if (mtd->writesize == 4096) |
||
359 | value &= ~S5P_NFCONF_PAGESIZE; |
||
360 | else |
||
361 | value |= S5P_NFCONF_PAGESIZE; |
||
362 | } |
||
363 | } |
||
364 | |||
365 | static void s5p_nand_inithw(struct s5p_nand_host *host) |
||
366 | { |
||
367 | u32 value; |
||
368 | |||
369 | /* Enable NAND Flash Controller */ |
||
370 | value = readl(host->nf_base + S5P_NFCONT); |
||
371 | writel(value | S5P_NFCONT_MODE, host->nf_base + S5P_NFCONT); |
||
372 | } |
||
373 | |||
374 | static void s5p_nand_parse_dt(struct s5p_nand_host *host, struct device *dev) |
||
375 | { |
||
376 | host->cpu_type = (enum s5p_cpu_type)of_device_get_match_data(dev); |
||
377 | } |
||
378 | |||
379 | static int s5p_nand_probe(struct platform_device *pdev) |
||
380 | { |
||
381 | int ret; |
||
382 | struct s5p_nand_host *host; |
||
383 | struct nand_chip *nand_chip; |
||
384 | struct mtd_info *mtd; |
||
385 | struct resource *mem; |
||
386 | |||
387 | /* Allocate memory for the device structure (and zero it) */ |
||
388 | host = devm_kzalloc(&pdev->dev, sizeof(struct s5p_nand_host), GFP_KERNEL); |
||
389 | if (!host) |
||
390 | return -ENOMEM; |
||
391 | |||
392 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
||
393 | host->nf_base = devm_ioremap_resource(&pdev->dev, mem); |
||
394 | if (IS_ERR(host->nf_base)) |
||
395 | return PTR_ERR(host->nf_base); |
||
396 | |||
397 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
||
398 | host->ecc_base = devm_ioremap_resource(&pdev->dev, mem); |
||
399 | if (IS_ERR(host->ecc_base)) |
||
400 | return PTR_ERR(host->ecc_base); |
||
401 | |||
402 | nand_chip = &host->nand_chip; |
||
403 | nand_chip->priv = host; |
||
404 | nand_set_flash_node(nand_chip, pdev->dev.of_node); |
||
405 | |||
406 | mtd = nand_to_mtd(nand_chip); |
||
407 | mtd->dev.parent = &pdev->dev; |
||
408 | mtd->priv = nand_chip; |
||
409 | |||
410 | /* Disable chip select and Enable NAND Flash Controller */ |
||
411 | writel((0x1 << 1) | (0x1 << 0), host->nf_base + S5P_NFCONT); |
||
412 | |||
413 | /* Set address of NAND IO lines */ |
||
414 | nand_chip->IO_ADDR_R = host->nf_base + S5P_NFDATA; |
||
415 | nand_chip->IO_ADDR_W = host->nf_base + S5P_NFDATA; |
||
416 | |||
417 | platform_set_drvdata(pdev, host); |
||
418 | |||
419 | /* get the clock source and enable it */ |
||
420 | host->clk[0] = devm_clk_get(&pdev->dev, "nandxl"); |
||
421 | if (IS_ERR(host->clk[0])) { |
||
422 | dev_err(&pdev->dev, "cannot get clock of nandxl\n"); |
||
423 | return -ENOENT; |
||
424 | } |
||
425 | clk_prepare_enable(host->clk[0]); |
||
426 | |||
427 | host->clk[1] = devm_clk_get(&pdev->dev, "nand"); |
||
428 | if (IS_ERR(host->clk[1])) { |
||
429 | dev_err(&pdev->dev, "cannot get clock of nand\n"); |
||
430 | return -ENOENT; |
||
431 | } |
||
432 | clk_prepare_enable(host->clk[1]); |
||
433 | |||
434 | s5p_nand_parse_dt(host, &pdev->dev); |
||
435 | |||
436 | nand_chip->select_chip = s3_nand_select_chip; |
||
437 | nand_chip->cmd_ctrl = s5p_cmd_ctrl; |
||
438 | nand_chip->dev_ready = s5p_nand_device_ready; |
||
439 | |||
440 | s5p_nand_inithw(host); |
||
441 | |||
442 | ret = nand_scan_ident(mtd, 1, NULL); |
||
443 | if (ret) |
||
444 | return ret; |
||
445 | |||
446 | if (nand_chip->ecc.mode == NAND_ECC_HW) { |
||
447 | nand_chip->ecc.correct = s5pcxx_nand_correct_data; |
||
448 | nand_chip->ecc.calculate = s5pcxx_nand_calculate_ecc; |
||
449 | nand_chip->ecc.hwctl = s5pcxx_nand_enable_hwecc; |
||
450 | nand_chip->ecc.read_page = s5pcxx_nand_read_page_hwecc; |
||
451 | |||
452 | nand_chip->ecc.size = 512; |
||
453 | nand_chip->ecc.bytes = 13; |
||
454 | |||
455 | mtd_set_ooblayout(nand_to_mtd(nand_chip), &s5pcxx_ooblayout_ops); |
||
456 | } |
||
457 | |||
458 | ret = nand_scan_tail(mtd); |
||
459 | if (ret) |
||
460 | return ret; |
||
461 | |||
462 | /* After you get the actual hardware information */ |
||
463 | s5p_nand_inithw_later(mtd); |
||
464 | |||
465 | return mtd_device_parse_register(mtd, NULL, NULL, NULL, 0); |
||
466 | } |
||
467 | |||
468 | static int s5p_nand_remove(struct platform_device *pdev) |
||
469 | { |
||
470 | struct s5p_nand_host *host = platform_get_drvdata(pdev); |
||
471 | struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); |
||
472 | |||
473 | nand_release(mtd); |
||
474 | clk_disable_unprepare(host->clk[0]); /* nandxl */ |
||
475 | clk_disable_unprepare(host->clk[1]); /* nand */ |
||
476 | |||
477 | return 0; |
||
478 | } |
||
479 | |||
480 | static const struct of_device_id s5p_nand_match[] = { |
||
481 | { .compatible = "samsung,s5pv210-nand", .data = TYPE_S5PV210 }, |
||
482 | {}, |
||
483 | }; |
||
484 | MODULE_DEVICE_TABLE(of, s5p_nand_match); |
||
485 | |||
486 | static struct platform_driver s5p_nand_driver = { |
||
487 | .probe = s5p_nand_probe, |
||
488 | .remove = s5p_nand_remove, |
||
489 | .driver = { |
||
490 | .name = "s5p-nand", |
||
491 | .owner = THIS_MODULE, |
||
492 | .of_match_table = s5p_nand_match, |
||
493 | }, |
||
494 | }; |
||
495 | module_platform_driver(s5p_nand_driver); |
||
496 | |||
497 | MODULE_LICENSE("GPL"); |
||
498 | MODULE_AUTHOR("Jianhui Zhao <jianhuizhao329@gmail.com>"); |
||
499 | MODULE_DESCRIPTION("S5Pxx MTD NAND driver"); |