aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mtd/lpddr
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-06-11 11:35:34 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2014-06-11 11:35:34 -0400
commite413a19a8ef49ae3b76310bb569dabe66b22f5a3 (patch)
treef171d40fd0ec69296458173d7ec470339f93f53b /drivers/mtd/lpddr
parent8d0304e69dc960ae7683943ac5b9c4c685d409d7 (diff)
parentf1900c79633e9ed757319e63aefb8e29443ea35e (diff)
Merge tag 'for-linus-20140610' of git://git.infradead.org/linux-mtd
Pull MTD updates from Brian Norris: - refactor m25p80.c driver for use as a general SPI NOR framework for other drivers which may speak to SPI NOR flash without providing full SPI support (i.e., not part of drivers/spi/) - new Freescale QuadSPI driver (utilizing new SPI NOR framework) - updates for the STMicro "FSM" SPI NOR driver - fix sync/flush behavior on mtd_blkdevs - fixup subpage write support on a few NAND drivers - correct the MTD OOB test for odd-sized OOB areas - add BCH-16 support for OMAP NAND - fix warnings and trivial refactoring - utilize new ECC DT bindings in pxa3xx NAND driver - new LPDDR NVM driver - address a few assorted bugs caught by Coverity - add new imx6sx support for GPMI NAND - use a bounce buffer for NAND when non-DMA-able buffers are used * tag 'for-linus-20140610' of git://git.infradead.org/linux-mtd: (77 commits) mtd: gpmi: add gpmi support for imx6sx mtd: maps: remove check for CONFIG_MTD_SUPERH_RESERVE mtd: bf5xx_nand: use the managed version of kzalloc mtd: pxa3xx_nand: make the driver work on big-endian systems mtd: nand: omap: fix omap_calculate_ecc_bch() for-loop error mtd: nand: r852: correct write_buf loop bounds mtd: nand_bbt: handle error case for nand_create_badblock_pattern() mtd: nand_bbt: remove unused variable mtd: maps: sc520cdp: fix warnings mtd: slram: fix unused variable warning mtd: pfow: remove unused variable mtd: lpddr: fix Kconfig dependency, for I/O accessors mtd: nand: pxa3xx: Add supported ECC strength and step size to the DT binding mtd: nand: pxa3xx: Use ECC strength and step size devicetree binding mtd: nand: pxa3xx: Clean pxa_ecc_init() error handling mtd: nand: Warn the user if the selected ECC strength is too weak mtd: nand: omap: Documentation: How to select correct ECC scheme for your device ? mtd: nand: omap: add support for BCH16_ECC - NAND driver updates mtd: nand: omap: add support for BCH16_ECC - ELM driver updates mtd: nand: omap: add support for BCH16_ECC - GPMC driver updates ...
Diffstat (limited to 'drivers/mtd/lpddr')
-rw-r--r--drivers/mtd/lpddr/Kconfig13
-rw-r--r--drivers/mtd/lpddr/Makefile1
-rw-r--r--drivers/mtd/lpddr/lpddr2_nvm.c507
3 files changed, 519 insertions, 2 deletions
diff --git a/drivers/mtd/lpddr/Kconfig b/drivers/mtd/lpddr/Kconfig
index 265f969817e3..3a19cbee24d7 100644
--- a/drivers/mtd/lpddr/Kconfig
+++ b/drivers/mtd/lpddr/Kconfig
@@ -1,5 +1,5 @@
1menu "LPDDR flash memory drivers" 1menu "LPDDR & LPDDR2 PCM memory drivers"
2 depends on MTD!=n 2 depends on MTD
3 3
4config MTD_LPDDR 4config MTD_LPDDR
5 tristate "Support for LPDDR flash chips" 5 tristate "Support for LPDDR flash chips"
@@ -17,4 +17,13 @@ config MTD_QINFO_PROBE
17 Window QINFO interface, permits software to be used for entire 17 Window QINFO interface, permits software to be used for entire
18 families of devices. This serves similar purpose of CFI on legacy 18 families of devices. This serves similar purpose of CFI on legacy
19 Flash products 19 Flash products
20
21config MTD_LPDDR2_NVM
22 # ARM dependency is only for writel_relaxed()
23 depends on MTD && ARM
24 tristate "Support for LPDDR2-NVM flash chips"
25 help
26 This option enables support of PCM memories with a LPDDR2-NVM
27 (Low power double data rate 2) interface.
28
20endmenu 29endmenu
diff --git a/drivers/mtd/lpddr/Makefile b/drivers/mtd/lpddr/Makefile
index da48e46b5812..881d440d483e 100644
--- a/drivers/mtd/lpddr/Makefile
+++ b/drivers/mtd/lpddr/Makefile
@@ -4,3 +4,4 @@
4 4
5obj-$(CONFIG_MTD_QINFO_PROBE) += qinfo_probe.o 5obj-$(CONFIG_MTD_QINFO_PROBE) += qinfo_probe.o
6obj-$(CONFIG_MTD_LPDDR) += lpddr_cmds.o 6obj-$(CONFIG_MTD_LPDDR) += lpddr_cmds.o
7obj-$(CONFIG_MTD_LPDDR2_NVM) += lpddr2_nvm.o
diff --git a/drivers/mtd/lpddr/lpddr2_nvm.c b/drivers/mtd/lpddr/lpddr2_nvm.c
new file mode 100644
index 000000000000..063cec40d0ae
--- /dev/null
+++ b/drivers/mtd/lpddr/lpddr2_nvm.c
@@ -0,0 +1,507 @@
1/*
2 * LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock
3 * support for LPDDR2-NVM PCM memories
4 *
5 * Copyright © 2012 Micron Technology, Inc.
6 *
7 * Vincenzo Aliberti <vincenzo.aliberti@gmail.com>
8 * Domenico Manna <domenico.manna@gmail.com>
9 * Many thanks to Andrea Vigilante for initial enabling
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 */
21
22#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
23
24#include <linux/init.h>
25#include <linux/io.h>
26#include <linux/module.h>
27#include <linux/kernel.h>
28#include <linux/mtd/map.h>
29#include <linux/mtd/mtd.h>
30#include <linux/mtd/partitions.h>
31#include <linux/slab.h>
32#include <linux/platform_device.h>
33#include <linux/ioport.h>
34#include <linux/err.h>
35
36/* Parameters */
37#define ERASE_BLOCKSIZE (0x00020000/2) /* in Word */
38#define WRITE_BUFFSIZE (0x00000400/2) /* in Word */
39#define OW_BASE_ADDRESS 0x00000000 /* OW offset */
40#define BUS_WIDTH 0x00000020 /* x32 devices */
41
42/* PFOW symbols address offset */
43#define PFOW_QUERY_STRING_P (0x0000/2) /* in Word */
44#define PFOW_QUERY_STRING_F (0x0002/2) /* in Word */
45#define PFOW_QUERY_STRING_O (0x0004/2) /* in Word */
46#define PFOW_QUERY_STRING_W (0x0006/2) /* in Word */
47
48/* OW registers address */
49#define CMD_CODE_OFS (0x0080/2) /* in Word */
50#define CMD_DATA_OFS (0x0084/2) /* in Word */
51#define CMD_ADD_L_OFS (0x0088/2) /* in Word */
52#define CMD_ADD_H_OFS (0x008A/2) /* in Word */
53#define MPR_L_OFS (0x0090/2) /* in Word */
54#define MPR_H_OFS (0x0092/2) /* in Word */
55#define CMD_EXEC_OFS (0x00C0/2) /* in Word */
56#define STATUS_REG_OFS (0x00CC/2) /* in Word */
57#define PRG_BUFFER_OFS (0x0010/2) /* in Word */
58
59/* Datamask */
60#define MR_CFGMASK 0x8000
61#define SR_OK_DATAMASK 0x0080
62
63/* LPDDR2-NVM Commands */
64#define LPDDR2_NVM_LOCK 0x0061
65#define LPDDR2_NVM_UNLOCK 0x0062
66#define LPDDR2_NVM_SW_PROGRAM 0x0041
67#define LPDDR2_NVM_SW_OVERWRITE 0x0042
68#define LPDDR2_NVM_BUF_PROGRAM 0x00E9
69#define LPDDR2_NVM_BUF_OVERWRITE 0x00EA
70#define LPDDR2_NVM_ERASE 0x0020
71
72/* LPDDR2-NVM Registers offset */
73#define LPDDR2_MODE_REG_DATA 0x0040
74#define LPDDR2_MODE_REG_CFG 0x0050
75
76/*
77 * Internal Type Definitions
78 * pcm_int_data contains memory controller details:
79 * @reg_data : LPDDR2_MODE_REG_DATA register address after remapping
80 * @reg_cfg : LPDDR2_MODE_REG_CFG register address after remapping
81 * &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes)
82 */
83struct pcm_int_data {
84 void __iomem *ctl_regs;
85 int bus_width;
86};
87
88static DEFINE_MUTEX(lpdd2_nvm_mutex);
89
90/*
91 * Build a map_word starting from an u_long
92 */
93static inline map_word build_map_word(u_long myword)
94{
95 map_word val = { {0} };
96 val.x[0] = myword;
97 return val;
98}
99
100/*
101 * Build Mode Register Configuration DataMask based on device bus-width
102 */
103static inline u_int build_mr_cfgmask(u_int bus_width)
104{
105 u_int val = MR_CFGMASK;
106
107 if (bus_width == 0x0004) /* x32 device */
108 val = val << 16;
109
110 return val;
111}
112
113/*
114 * Build Status Register OK DataMask based on device bus-width
115 */
116static inline u_int build_sr_ok_datamask(u_int bus_width)
117{
118 u_int val = SR_OK_DATAMASK;
119
120 if (bus_width == 0x0004) /* x32 device */
121 val = (val << 16)+val;
122
123 return val;
124}
125
126/*
127 * Evaluates Overlay Window Control Registers address
128 */
129static inline u_long ow_reg_add(struct map_info *map, u_long offset)
130{
131 u_long val = 0;
132 struct pcm_int_data *pcm_data = map->fldrv_priv;
133
134 val = map->pfow_base + offset*pcm_data->bus_width;
135
136 return val;
137}
138
139/*
140 * Enable lpddr2-nvm Overlay Window
141 * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
142 * used by device commands as well as uservisible resources like Device Status
143 * Register, Device ID, etc
144 */
145static inline void ow_enable(struct map_info *map)
146{
147 struct pcm_int_data *pcm_data = map->fldrv_priv;
148
149 writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
150 pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
151 writel_relaxed(0x01, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
152}
153
154/*
155 * Disable lpddr2-nvm Overlay Window
156 * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
157 * used by device commands as well as uservisible resources like Device Status
158 * Register, Device ID, etc
159 */
160static inline void ow_disable(struct map_info *map)
161{
162 struct pcm_int_data *pcm_data = map->fldrv_priv;
163
164 writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
165 pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
166 writel_relaxed(0x02, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
167}
168
169/*
170 * Execute lpddr2-nvm operations
171 */
172static int lpddr2_nvm_do_op(struct map_info *map, u_long cmd_code,
173 u_long cmd_data, u_long cmd_add, u_long cmd_mpr, u_char *buf)
174{
175 map_word add_l = { {0} }, add_h = { {0} }, mpr_l = { {0} },
176 mpr_h = { {0} }, data_l = { {0} }, cmd = { {0} },
177 exec_cmd = { {0} }, sr;
178 map_word data_h = { {0} }; /* only for 2x x16 devices stacked */
179 u_long i, status_reg, prg_buff_ofs;
180 struct pcm_int_data *pcm_data = map->fldrv_priv;
181 u_int sr_ok_datamask = build_sr_ok_datamask(pcm_data->bus_width);
182
183 /* Builds low and high words for OW Control Registers */
184 add_l.x[0] = cmd_add & 0x0000FFFF;
185 add_h.x[0] = (cmd_add >> 16) & 0x0000FFFF;
186 mpr_l.x[0] = cmd_mpr & 0x0000FFFF;
187 mpr_h.x[0] = (cmd_mpr >> 16) & 0x0000FFFF;
188 cmd.x[0] = cmd_code & 0x0000FFFF;
189 exec_cmd.x[0] = 0x0001;
190 data_l.x[0] = cmd_data & 0x0000FFFF;
191 data_h.x[0] = (cmd_data >> 16) & 0x0000FFFF; /* only for 2x x16 */
192
193 /* Set Overlay Window Control Registers */
194 map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS));
195 map_write(map, data_l, ow_reg_add(map, CMD_DATA_OFS));
196 map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS));
197 map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS));
198 map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS));
199 map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS));
200 if (pcm_data->bus_width == 0x0004) { /* 2x16 devices stacked */
201 map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS) + 2);
202 map_write(map, data_h, ow_reg_add(map, CMD_DATA_OFS) + 2);
203 map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS) + 2);
204 map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS) + 2);
205 map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS) + 2);
206 map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS) + 2);
207 }
208
209 /* Fill Program Buffer */
210 if ((cmd_code == LPDDR2_NVM_BUF_PROGRAM) ||
211 (cmd_code == LPDDR2_NVM_BUF_OVERWRITE)) {
212 prg_buff_ofs = (map_read(map,
213 ow_reg_add(map, PRG_BUFFER_OFS))).x[0];
214 for (i = 0; i < cmd_mpr; i++) {
215 map_write(map, build_map_word(buf[i]), map->pfow_base +
216 prg_buff_ofs + i);
217 }
218 }
219
220 /* Command Execute */
221 map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS));
222 if (pcm_data->bus_width == 0x0004) /* 2x16 devices stacked */
223 map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS) + 2);
224
225 /* Status Register Check */
226 do {
227 sr = map_read(map, ow_reg_add(map, STATUS_REG_OFS));
228 status_reg = sr.x[0];
229 if (pcm_data->bus_width == 0x0004) {/* 2x16 devices stacked */
230 sr = map_read(map, ow_reg_add(map,
231 STATUS_REG_OFS) + 2);
232 status_reg += sr.x[0] << 16;
233 }
234 } while ((status_reg & sr_ok_datamask) != sr_ok_datamask);
235
236 return (((status_reg & sr_ok_datamask) == sr_ok_datamask) ? 0 : -EIO);
237}
238
239/*
240 * Execute lpddr2-nvm operations @ block level
241 */
242static int lpddr2_nvm_do_block_op(struct mtd_info *mtd, loff_t start_add,
243 uint64_t len, u_char block_op)
244{
245 struct map_info *map = mtd->priv;
246 u_long add, end_add;
247 int ret = 0;
248
249 mutex_lock(&lpdd2_nvm_mutex);
250
251 ow_enable(map);
252
253 add = start_add;
254 end_add = add + len;
255
256 do {
257 ret = lpddr2_nvm_do_op(map, block_op, 0x00, add, add, NULL);
258 if (ret)
259 goto out;
260 add += mtd->erasesize;
261 } while (add < end_add);
262
263out:
264 ow_disable(map);
265 mutex_unlock(&lpdd2_nvm_mutex);
266 return ret;
267}
268
269/*
270 * verify presence of PFOW string
271 */
272static int lpddr2_nvm_pfow_present(struct map_info *map)
273{
274 map_word pfow_val[4];
275 unsigned int found = 1;
276
277 mutex_lock(&lpdd2_nvm_mutex);
278
279 ow_enable(map);
280
281 /* Load string from array */
282 pfow_val[0] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_P));
283 pfow_val[1] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_F));
284 pfow_val[2] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_O));
285 pfow_val[3] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_W));
286
287 /* Verify the string loaded vs expected */
288 if (!map_word_equal(map, build_map_word('P'), pfow_val[0]))
289 found = 0;
290 if (!map_word_equal(map, build_map_word('F'), pfow_val[1]))
291 found = 0;
292 if (!map_word_equal(map, build_map_word('O'), pfow_val[2]))
293 found = 0;
294 if (!map_word_equal(map, build_map_word('W'), pfow_val[3]))
295 found = 0;
296
297 ow_disable(map);
298
299 mutex_unlock(&lpdd2_nvm_mutex);
300
301 return found;
302}
303
304/*
305 * lpddr2_nvm driver read method
306 */
307static int lpddr2_nvm_read(struct mtd_info *mtd, loff_t start_add,
308 size_t len, size_t *retlen, u_char *buf)
309{
310 struct map_info *map = mtd->priv;
311
312 mutex_lock(&lpdd2_nvm_mutex);
313
314 *retlen = len;
315
316 map_copy_from(map, buf, start_add, *retlen);
317
318 mutex_unlock(&lpdd2_nvm_mutex);
319 return 0;
320}
321
322/*
323 * lpddr2_nvm driver write method
324 */
325static int lpddr2_nvm_write(struct mtd_info *mtd, loff_t start_add,
326 size_t len, size_t *retlen, const u_char *buf)
327{
328 struct map_info *map = mtd->priv;
329 struct pcm_int_data *pcm_data = map->fldrv_priv;
330 u_long add, current_len, tot_len, target_len, my_data;
331 u_char *write_buf = (u_char *)buf;
332 int ret = 0;
333
334 mutex_lock(&lpdd2_nvm_mutex);
335
336 ow_enable(map);
337
338 /* Set start value for the variables */
339 add = start_add;
340 target_len = len;
341 tot_len = 0;
342
343 while (tot_len < target_len) {
344 if (!(IS_ALIGNED(add, mtd->writesize))) { /* do sw program */
345 my_data = write_buf[tot_len];
346 my_data += (write_buf[tot_len+1]) << 8;
347 if (pcm_data->bus_width == 0x0004) {/* 2x16 devices */
348 my_data += (write_buf[tot_len+2]) << 16;
349 my_data += (write_buf[tot_len+3]) << 24;
350 }
351 ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_SW_OVERWRITE,
352 my_data, add, 0x00, NULL);
353 if (ret)
354 goto out;
355
356 add += pcm_data->bus_width;
357 tot_len += pcm_data->bus_width;
358 } else { /* do buffer program */
359 current_len = min(target_len - tot_len,
360 (u_long) mtd->writesize);
361 ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_BUF_OVERWRITE,
362 0x00, add, current_len, write_buf + tot_len);
363 if (ret)
364 goto out;
365
366 add += current_len;
367 tot_len += current_len;
368 }
369 }
370
371out:
372 *retlen = tot_len;
373 ow_disable(map);
374 mutex_unlock(&lpdd2_nvm_mutex);
375 return ret;
376}
377
378/*
379 * lpddr2_nvm driver erase method
380 */
381static int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr)
382{
383 int ret = lpddr2_nvm_do_block_op(mtd, instr->addr, instr->len,
384 LPDDR2_NVM_ERASE);
385 if (!ret) {
386 instr->state = MTD_ERASE_DONE;
387 mtd_erase_callback(instr);
388 }
389
390 return ret;
391}
392
393/*
394 * lpddr2_nvm driver unlock method
395 */
396static int lpddr2_nvm_unlock(struct mtd_info *mtd, loff_t start_add,
397 uint64_t len)
398{
399 return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_UNLOCK);
400}
401
402/*
403 * lpddr2_nvm driver lock method
404 */
405static int lpddr2_nvm_lock(struct mtd_info *mtd, loff_t start_add,
406 uint64_t len)
407{
408 return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_LOCK);
409}
410
411/*
412 * lpddr2_nvm driver probe method
413 */
414static int lpddr2_nvm_probe(struct platform_device *pdev)
415{
416 struct map_info *map;
417 struct mtd_info *mtd;
418 struct resource *add_range;
419 struct resource *control_regs;
420 struct pcm_int_data *pcm_data;
421
422 /* Allocate memory control_regs data structures */
423 pcm_data = devm_kzalloc(&pdev->dev, sizeof(*pcm_data), GFP_KERNEL);
424 if (!pcm_data)
425 return -ENOMEM;
426
427 pcm_data->bus_width = BUS_WIDTH;
428
429 /* Allocate memory for map_info & mtd_info data structures */
430 map = devm_kzalloc(&pdev->dev, sizeof(*map), GFP_KERNEL);
431 if (!map)
432 return -ENOMEM;
433
434 mtd = devm_kzalloc(&pdev->dev, sizeof(*mtd), GFP_KERNEL);
435 if (!mtd)
436 return -ENOMEM;
437
438 /* lpddr2_nvm address range */
439 add_range = platform_get_resource(pdev, IORESOURCE_MEM, 0);
440
441 /* Populate map_info data structure */
442 *map = (struct map_info) {
443 .virt = devm_ioremap_resource(&pdev->dev, add_range),
444 .name = pdev->dev.init_name,
445 .phys = add_range->start,
446 .size = resource_size(add_range),
447 .bankwidth = pcm_data->bus_width / 2,
448 .pfow_base = OW_BASE_ADDRESS,
449 .fldrv_priv = pcm_data,
450 };
451 if (IS_ERR(map->virt))
452 return PTR_ERR(map->virt);
453
454 simple_map_init(map); /* fill with default methods */
455
456 control_regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
457 pcm_data->ctl_regs = devm_ioremap_resource(&pdev->dev, control_regs);
458 if (IS_ERR(pcm_data->ctl_regs))
459 return PTR_ERR(pcm_data->ctl_regs);
460
461 /* Populate mtd_info data structure */
462 *mtd = (struct mtd_info) {
463 .name = pdev->dev.init_name,
464 .type = MTD_RAM,
465 .priv = map,
466 .size = resource_size(add_range),
467 .erasesize = ERASE_BLOCKSIZE * pcm_data->bus_width,
468 .writesize = 1,
469 .writebufsize = WRITE_BUFFSIZE * pcm_data->bus_width,
470 .flags = (MTD_CAP_NVRAM | MTD_POWERUP_LOCK),
471 ._read = lpddr2_nvm_read,
472 ._write = lpddr2_nvm_write,
473 ._erase = lpddr2_nvm_erase,
474 ._unlock = lpddr2_nvm_unlock,
475 ._lock = lpddr2_nvm_lock,
476 };
477
478 /* Verify the presence of the device looking for PFOW string */
479 if (!lpddr2_nvm_pfow_present(map)) {
480 pr_err("device not recognized\n");
481 return -EINVAL;
482 }
483 /* Parse partitions and register the MTD device */
484 return mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
485}
486
487/*
488 * lpddr2_nvm driver remove method
489 */
490static int lpddr2_nvm_remove(struct platform_device *pdev)
491{
492 return mtd_device_unregister(dev_get_drvdata(&pdev->dev));
493}
494
495/* Initialize platform_driver data structure for lpddr2_nvm */
496static struct platform_driver lpddr2_nvm_drv = {
497 .driver = {
498 .name = "lpddr2_nvm",
499 },
500 .probe = lpddr2_nvm_probe,
501 .remove = lpddr2_nvm_remove,
502};
503
504module_platform_driver(lpddr2_nvm_drv);
505MODULE_LICENSE("GPL");
506MODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>");
507MODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories");