1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock |
4 | * support for LPDDR2-NVM PCM memories |
5 | * |
6 | * Copyright © 2012 Micron Technology, Inc. |
7 | * |
8 | * Vincenzo Aliberti <vincenzo.aliberti@gmail.com> |
9 | * Domenico Manna <domenico.manna@gmail.com> |
10 | * Many thanks to Andrea Vigilante for initial enabling |
11 | */ |
12 | |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ |
14 | |
15 | #include <linux/init.h> |
16 | #include <linux/io.h> |
17 | #include <linux/module.h> |
18 | #include <linux/kernel.h> |
19 | #include <linux/mtd/map.h> |
20 | #include <linux/mtd/mtd.h> |
21 | #include <linux/mtd/partitions.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/platform_device.h> |
24 | #include <linux/ioport.h> |
25 | #include <linux/err.h> |
26 | |
27 | /* Parameters */ |
28 | #define ERASE_BLOCKSIZE (0x00020000/2) /* in Word */ |
29 | #define WRITE_BUFFSIZE (0x00000400/2) /* in Word */ |
30 | #define OW_BASE_ADDRESS 0x00000000 /* OW offset */ |
31 | #define BUS_WIDTH 0x00000020 /* x32 devices */ |
32 | |
33 | /* PFOW symbols address offset */ |
34 | #define PFOW_QUERY_STRING_P (0x0000/2) /* in Word */ |
35 | #define PFOW_QUERY_STRING_F (0x0002/2) /* in Word */ |
36 | #define PFOW_QUERY_STRING_O (0x0004/2) /* in Word */ |
37 | #define PFOW_QUERY_STRING_W (0x0006/2) /* in Word */ |
38 | |
39 | /* OW registers address */ |
40 | #define CMD_CODE_OFS (0x0080/2) /* in Word */ |
41 | #define CMD_DATA_OFS (0x0084/2) /* in Word */ |
42 | #define CMD_ADD_L_OFS (0x0088/2) /* in Word */ |
43 | #define CMD_ADD_H_OFS (0x008A/2) /* in Word */ |
44 | #define MPR_L_OFS (0x0090/2) /* in Word */ |
45 | #define MPR_H_OFS (0x0092/2) /* in Word */ |
46 | #define CMD_EXEC_OFS (0x00C0/2) /* in Word */ |
47 | #define STATUS_REG_OFS (0x00CC/2) /* in Word */ |
48 | #define PRG_BUFFER_OFS (0x0010/2) /* in Word */ |
49 | |
50 | /* Datamask */ |
51 | #define MR_CFGMASK 0x8000 |
52 | #define SR_OK_DATAMASK 0x0080 |
53 | |
54 | /* LPDDR2-NVM Commands */ |
55 | #define LPDDR2_NVM_LOCK 0x0061 |
56 | #define LPDDR2_NVM_UNLOCK 0x0062 |
57 | #define LPDDR2_NVM_SW_PROGRAM 0x0041 |
58 | #define LPDDR2_NVM_SW_OVERWRITE 0x0042 |
59 | #define LPDDR2_NVM_BUF_PROGRAM 0x00E9 |
60 | #define LPDDR2_NVM_BUF_OVERWRITE 0x00EA |
61 | #define LPDDR2_NVM_ERASE 0x0020 |
62 | |
63 | /* LPDDR2-NVM Registers offset */ |
64 | #define LPDDR2_MODE_REG_DATA 0x0040 |
65 | #define LPDDR2_MODE_REG_CFG 0x0050 |
66 | |
67 | /* |
68 | * Internal Type Definitions |
69 | * pcm_int_data contains memory controller details: |
70 | * @reg_data : LPDDR2_MODE_REG_DATA register address after remapping |
71 | * @reg_cfg : LPDDR2_MODE_REG_CFG register address after remapping |
72 | * &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes) |
73 | */ |
74 | struct pcm_int_data { |
75 | void __iomem *ctl_regs; |
76 | int bus_width; |
77 | }; |
78 | |
79 | static DEFINE_MUTEX(lpdd2_nvm_mutex); |
80 | |
81 | /* |
82 | * Build a map_word starting from an u_long |
83 | */ |
84 | static inline map_word build_map_word(u_long myword) |
85 | { |
86 | map_word val = { {0} }; |
87 | val.x[0] = myword; |
88 | return val; |
89 | } |
90 | |
91 | /* |
92 | * Build Mode Register Configuration DataMask based on device bus-width |
93 | */ |
94 | static inline u_int build_mr_cfgmask(u_int bus_width) |
95 | { |
96 | u_int val = MR_CFGMASK; |
97 | |
98 | if (bus_width == 0x0004) /* x32 device */ |
99 | val = val << 16; |
100 | |
101 | return val; |
102 | } |
103 | |
104 | /* |
105 | * Build Status Register OK DataMask based on device bus-width |
106 | */ |
107 | static inline u_int build_sr_ok_datamask(u_int bus_width) |
108 | { |
109 | u_int val = SR_OK_DATAMASK; |
110 | |
111 | if (bus_width == 0x0004) /* x32 device */ |
112 | val = (val << 16)+val; |
113 | |
114 | return val; |
115 | } |
116 | |
117 | /* |
118 | * Evaluates Overlay Window Control Registers address |
119 | */ |
120 | static inline u_long ow_reg_add(struct map_info *map, u_long offset) |
121 | { |
122 | u_long val = 0; |
123 | struct pcm_int_data *pcm_data = map->fldrv_priv; |
124 | |
125 | val = map->pfow_base + offset*pcm_data->bus_width; |
126 | |
127 | return val; |
128 | } |
129 | |
130 | /* |
131 | * Enable lpddr2-nvm Overlay Window |
132 | * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers |
133 | * used by device commands as well as uservisible resources like Device Status |
134 | * Register, Device ID, etc |
135 | */ |
136 | static inline void ow_enable(struct map_info *map) |
137 | { |
138 | struct pcm_int_data *pcm_data = map->fldrv_priv; |
139 | |
140 | writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18, |
141 | pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG); |
142 | writel_relaxed(0x01, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA); |
143 | } |
144 | |
145 | /* |
146 | * Disable lpddr2-nvm Overlay Window |
147 | * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers |
148 | * used by device commands as well as uservisible resources like Device Status |
149 | * Register, Device ID, etc |
150 | */ |
151 | static inline void ow_disable(struct map_info *map) |
152 | { |
153 | struct pcm_int_data *pcm_data = map->fldrv_priv; |
154 | |
155 | writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18, |
156 | pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG); |
157 | writel_relaxed(0x02, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA); |
158 | } |
159 | |
160 | /* |
161 | * Execute lpddr2-nvm operations |
162 | */ |
163 | static int lpddr2_nvm_do_op(struct map_info *map, u_long cmd_code, |
164 | u_long cmd_data, u_long cmd_add, u_long cmd_mpr, u_char *buf) |
165 | { |
166 | map_word add_l = { {0} }, add_h = { {0} }, mpr_l = { {0} }, |
167 | mpr_h = { {0} }, data_l = { {0} }, cmd = { {0} }, |
168 | exec_cmd = { {0} }, sr; |
169 | map_word data_h = { {0} }; /* only for 2x x16 devices stacked */ |
170 | u_long i, status_reg, prg_buff_ofs; |
171 | struct pcm_int_data *pcm_data = map->fldrv_priv; |
172 | u_int sr_ok_datamask = build_sr_ok_datamask(bus_width: pcm_data->bus_width); |
173 | |
174 | /* Builds low and high words for OW Control Registers */ |
175 | add_l.x[0] = cmd_add & 0x0000FFFF; |
176 | add_h.x[0] = (cmd_add >> 16) & 0x0000FFFF; |
177 | mpr_l.x[0] = cmd_mpr & 0x0000FFFF; |
178 | mpr_h.x[0] = (cmd_mpr >> 16) & 0x0000FFFF; |
179 | cmd.x[0] = cmd_code & 0x0000FFFF; |
180 | exec_cmd.x[0] = 0x0001; |
181 | data_l.x[0] = cmd_data & 0x0000FFFF; |
182 | data_h.x[0] = (cmd_data >> 16) & 0x0000FFFF; /* only for 2x x16 */ |
183 | |
184 | /* Set Overlay Window Control Registers */ |
185 | map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS)); |
186 | map_write(map, data_l, ow_reg_add(map, CMD_DATA_OFS)); |
187 | map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS)); |
188 | map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS)); |
189 | map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS)); |
190 | map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS)); |
191 | if (pcm_data->bus_width == 0x0004) { /* 2x16 devices stacked */ |
192 | map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS) + 2); |
193 | map_write(map, data_h, ow_reg_add(map, CMD_DATA_OFS) + 2); |
194 | map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS) + 2); |
195 | map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS) + 2); |
196 | map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS) + 2); |
197 | map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS) + 2); |
198 | } |
199 | |
200 | /* Fill Program Buffer */ |
201 | if ((cmd_code == LPDDR2_NVM_BUF_PROGRAM) || |
202 | (cmd_code == LPDDR2_NVM_BUF_OVERWRITE)) { |
203 | prg_buff_ofs = (map_read(map, |
204 | ow_reg_add(map, PRG_BUFFER_OFS))).x[0]; |
205 | for (i = 0; i < cmd_mpr; i++) { |
206 | map_write(map, build_map_word(buf[i]), map->pfow_base + |
207 | prg_buff_ofs + i); |
208 | } |
209 | } |
210 | |
211 | /* Command Execute */ |
212 | map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS)); |
213 | if (pcm_data->bus_width == 0x0004) /* 2x16 devices stacked */ |
214 | map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS) + 2); |
215 | |
216 | /* Status Register Check */ |
217 | do { |
218 | sr = map_read(map, ow_reg_add(map, STATUS_REG_OFS)); |
219 | status_reg = sr.x[0]; |
220 | if (pcm_data->bus_width == 0x0004) {/* 2x16 devices stacked */ |
221 | sr = map_read(map, ow_reg_add(map, |
222 | STATUS_REG_OFS) + 2); |
223 | status_reg += sr.x[0] << 16; |
224 | } |
225 | } while ((status_reg & sr_ok_datamask) != sr_ok_datamask); |
226 | |
227 | return (((status_reg & sr_ok_datamask) == sr_ok_datamask) ? 0 : -EIO); |
228 | } |
229 | |
230 | /* |
231 | * Execute lpddr2-nvm operations @ block level |
232 | */ |
233 | static int lpddr2_nvm_do_block_op(struct mtd_info *mtd, loff_t start_add, |
234 | uint64_t len, u_char block_op) |
235 | { |
236 | struct map_info *map = mtd->priv; |
237 | u_long add, end_add; |
238 | int ret = 0; |
239 | |
240 | mutex_lock(&lpdd2_nvm_mutex); |
241 | |
242 | ow_enable(map); |
243 | |
244 | add = start_add; |
245 | end_add = add + len; |
246 | |
247 | do { |
248 | ret = lpddr2_nvm_do_op(map, cmd_code: block_op, cmd_data: 0x00, cmd_add: add, cmd_mpr: add, NULL); |
249 | if (ret) |
250 | goto out; |
251 | add += mtd->erasesize; |
252 | } while (add < end_add); |
253 | |
254 | out: |
255 | ow_disable(map); |
256 | mutex_unlock(lock: &lpdd2_nvm_mutex); |
257 | return ret; |
258 | } |
259 | |
260 | /* |
261 | * verify presence of PFOW string |
262 | */ |
263 | static int lpddr2_nvm_pfow_present(struct map_info *map) |
264 | { |
265 | map_word pfow_val[4]; |
266 | unsigned int found = 1; |
267 | |
268 | mutex_lock(&lpdd2_nvm_mutex); |
269 | |
270 | ow_enable(map); |
271 | |
272 | /* Load string from array */ |
273 | pfow_val[0] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_P)); |
274 | pfow_val[1] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_F)); |
275 | pfow_val[2] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_O)); |
276 | pfow_val[3] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_W)); |
277 | |
278 | /* Verify the string loaded vs expected */ |
279 | if (!map_word_equal(map, build_map_word('P'), pfow_val[0])) |
280 | found = 0; |
281 | if (!map_word_equal(map, build_map_word('F'), pfow_val[1])) |
282 | found = 0; |
283 | if (!map_word_equal(map, build_map_word('O'), pfow_val[2])) |
284 | found = 0; |
285 | if (!map_word_equal(map, build_map_word('W'), pfow_val[3])) |
286 | found = 0; |
287 | |
288 | ow_disable(map); |
289 | |
290 | mutex_unlock(lock: &lpdd2_nvm_mutex); |
291 | |
292 | return found; |
293 | } |
294 | |
295 | /* |
296 | * lpddr2_nvm driver read method |
297 | */ |
298 | static int lpddr2_nvm_read(struct mtd_info *mtd, loff_t start_add, |
299 | size_t len, size_t *retlen, u_char *buf) |
300 | { |
301 | struct map_info *map = mtd->priv; |
302 | |
303 | mutex_lock(&lpdd2_nvm_mutex); |
304 | |
305 | *retlen = len; |
306 | |
307 | map_copy_from(map, buf, start_add, *retlen); |
308 | |
309 | mutex_unlock(lock: &lpdd2_nvm_mutex); |
310 | return 0; |
311 | } |
312 | |
313 | /* |
314 | * lpddr2_nvm driver write method |
315 | */ |
316 | static int lpddr2_nvm_write(struct mtd_info *mtd, loff_t start_add, |
317 | size_t len, size_t *retlen, const u_char *buf) |
318 | { |
319 | struct map_info *map = mtd->priv; |
320 | struct pcm_int_data *pcm_data = map->fldrv_priv; |
321 | u_long add, current_len, tot_len, target_len, my_data; |
322 | u_char *write_buf = (u_char *)buf; |
323 | int ret = 0; |
324 | |
325 | mutex_lock(&lpdd2_nvm_mutex); |
326 | |
327 | ow_enable(map); |
328 | |
329 | /* Set start value for the variables */ |
330 | add = start_add; |
331 | target_len = len; |
332 | tot_len = 0; |
333 | |
334 | while (tot_len < target_len) { |
335 | if (!(IS_ALIGNED(add, mtd->writesize))) { /* do sw program */ |
336 | my_data = write_buf[tot_len]; |
337 | my_data += (write_buf[tot_len+1]) << 8; |
338 | if (pcm_data->bus_width == 0x0004) {/* 2x16 devices */ |
339 | my_data += (write_buf[tot_len+2]) << 16; |
340 | my_data += (write_buf[tot_len+3]) << 24; |
341 | } |
342 | ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_SW_OVERWRITE, |
343 | cmd_data: my_data, cmd_add: add, cmd_mpr: 0x00, NULL); |
344 | if (ret) |
345 | goto out; |
346 | |
347 | add += pcm_data->bus_width; |
348 | tot_len += pcm_data->bus_width; |
349 | } else { /* do buffer program */ |
350 | current_len = min(target_len - tot_len, |
351 | (u_long) mtd->writesize); |
352 | ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_BUF_OVERWRITE, |
353 | cmd_data: 0x00, cmd_add: add, cmd_mpr: current_len, buf: write_buf + tot_len); |
354 | if (ret) |
355 | goto out; |
356 | |
357 | add += current_len; |
358 | tot_len += current_len; |
359 | } |
360 | } |
361 | |
362 | out: |
363 | *retlen = tot_len; |
364 | ow_disable(map); |
365 | mutex_unlock(lock: &lpdd2_nvm_mutex); |
366 | return ret; |
367 | } |
368 | |
369 | /* |
370 | * lpddr2_nvm driver erase method |
371 | */ |
372 | static int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr) |
373 | { |
374 | return lpddr2_nvm_do_block_op(mtd, start_add: instr->addr, len: instr->len, |
375 | LPDDR2_NVM_ERASE); |
376 | } |
377 | |
378 | /* |
379 | * lpddr2_nvm driver unlock method |
380 | */ |
381 | static int lpddr2_nvm_unlock(struct mtd_info *mtd, loff_t start_add, |
382 | uint64_t len) |
383 | { |
384 | return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_UNLOCK); |
385 | } |
386 | |
387 | /* |
388 | * lpddr2_nvm driver lock method |
389 | */ |
390 | static int lpddr2_nvm_lock(struct mtd_info *mtd, loff_t start_add, |
391 | uint64_t len) |
392 | { |
393 | return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_LOCK); |
394 | } |
395 | |
396 | static const struct mtd_info lpddr2_nvm_mtd_info = { |
397 | .type = MTD_RAM, |
398 | .writesize = 1, |
399 | .flags = (MTD_CAP_NVRAM | MTD_POWERUP_LOCK), |
400 | ._read = lpddr2_nvm_read, |
401 | ._write = lpddr2_nvm_write, |
402 | ._erase = lpddr2_nvm_erase, |
403 | ._unlock = lpddr2_nvm_unlock, |
404 | ._lock = lpddr2_nvm_lock, |
405 | }; |
406 | |
407 | /* |
408 | * lpddr2_nvm driver probe method |
409 | */ |
410 | static int lpddr2_nvm_probe(struct platform_device *pdev) |
411 | { |
412 | struct map_info *map; |
413 | struct mtd_info *mtd; |
414 | struct resource *add_range; |
415 | struct pcm_int_data *pcm_data; |
416 | |
417 | /* Allocate memory control_regs data structures */ |
418 | pcm_data = devm_kzalloc(dev: &pdev->dev, size: sizeof(*pcm_data), GFP_KERNEL); |
419 | if (!pcm_data) |
420 | return -ENOMEM; |
421 | |
422 | pcm_data->bus_width = BUS_WIDTH; |
423 | |
424 | /* Allocate memory for map_info & mtd_info data structures */ |
425 | map = devm_kzalloc(dev: &pdev->dev, size: sizeof(*map), GFP_KERNEL); |
426 | if (!map) |
427 | return -ENOMEM; |
428 | |
429 | mtd = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mtd), GFP_KERNEL); |
430 | if (!mtd) |
431 | return -ENOMEM; |
432 | |
433 | /* lpddr2_nvm address range */ |
434 | add_range = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
435 | if (!add_range) |
436 | return -ENODEV; |
437 | |
438 | /* Populate map_info data structure */ |
439 | *map = (struct map_info) { |
440 | .virt = devm_ioremap_resource(dev: &pdev->dev, res: add_range), |
441 | .name = pdev->dev.init_name, |
442 | .phys = add_range->start, |
443 | .size = resource_size(res: add_range), |
444 | .bankwidth = pcm_data->bus_width / 2, |
445 | .pfow_base = OW_BASE_ADDRESS, |
446 | .fldrv_priv = pcm_data, |
447 | }; |
448 | |
449 | if (IS_ERR(ptr: map->virt)) |
450 | return PTR_ERR(ptr: map->virt); |
451 | |
452 | simple_map_init(map); /* fill with default methods */ |
453 | |
454 | pcm_data->ctl_regs = devm_platform_ioremap_resource(pdev, index: 1); |
455 | if (IS_ERR(ptr: pcm_data->ctl_regs)) |
456 | return PTR_ERR(ptr: pcm_data->ctl_regs); |
457 | |
458 | /* Populate mtd_info data structure */ |
459 | *mtd = lpddr2_nvm_mtd_info; |
460 | mtd->dev.parent = &pdev->dev; |
461 | mtd->name = pdev->dev.init_name; |
462 | mtd->priv = map; |
463 | mtd->size = resource_size(res: add_range); |
464 | mtd->erasesize = ERASE_BLOCKSIZE * pcm_data->bus_width; |
465 | mtd->writebufsize = WRITE_BUFFSIZE * pcm_data->bus_width; |
466 | |
467 | /* Verify the presence of the device looking for PFOW string */ |
468 | if (!lpddr2_nvm_pfow_present(map)) { |
469 | pr_err("device not recognized\n" ); |
470 | return -EINVAL; |
471 | } |
472 | /* Parse partitions and register the MTD device */ |
473 | return mtd_device_register(mtd, NULL, 0); |
474 | } |
475 | |
476 | /* |
477 | * lpddr2_nvm driver remove method |
478 | */ |
479 | static void lpddr2_nvm_remove(struct platform_device *pdev) |
480 | { |
481 | WARN_ON(mtd_device_unregister(dev_get_drvdata(&pdev->dev))); |
482 | } |
483 | |
484 | /* Initialize platform_driver data structure for lpddr2_nvm */ |
485 | static struct platform_driver lpddr2_nvm_drv = { |
486 | .driver = { |
487 | .name = "lpddr2_nvm" , |
488 | }, |
489 | .probe = lpddr2_nvm_probe, |
490 | .remove_new = lpddr2_nvm_remove, |
491 | }; |
492 | |
493 | module_platform_driver(lpddr2_nvm_drv); |
494 | MODULE_LICENSE("GPL" ); |
495 | MODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>" ); |
496 | MODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories" ); |
497 | |