497 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock4 * support for LPDDR2-NVM PCM memories5 *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 enabling11 */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 0x800052#define SR_OK_DATAMASK 0x008053 54/* LPDDR2-NVM Commands */55#define LPDDR2_NVM_LOCK 0x006156#define LPDDR2_NVM_UNLOCK 0x006257#define LPDDR2_NVM_SW_PROGRAM 0x004158#define LPDDR2_NVM_SW_OVERWRITE 0x004259#define LPDDR2_NVM_BUF_PROGRAM 0x00E960#define LPDDR2_NVM_BUF_OVERWRITE 0x00EA61#define LPDDR2_NVM_ERASE 0x002062 63/* LPDDR2-NVM Registers offset */64#define LPDDR2_MODE_REG_DATA 0x004065#define LPDDR2_MODE_REG_CFG 0x005066 67/*68 * Internal Type Definitions69 * pcm_int_data contains memory controller details:70 * @reg_data : LPDDR2_MODE_REG_DATA register address after remapping71 * @reg_cfg : LPDDR2_MODE_REG_CFG register address after remapping72 * &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes)73 */74struct pcm_int_data {75 void __iomem *ctl_regs;76 int bus_width;77};78 79static DEFINE_MUTEX(lpdd2_nvm_mutex);80 81/*82 * Build a map_word starting from an u_long83 */84static 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-width93 */94static 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-width106 */107static 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 address119 */120static 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 Window132 * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers133 * used by device commands as well as uservisible resources like Device Status134 * Register, Device ID, etc135 */136static 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 Window147 * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers148 * used by device commands as well as uservisible resources like Device Status149 * Register, Device ID, etc150 */151static 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 operations162 */163static 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(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 level232 */233static 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, block_op, 0x00, add, add, NULL);249 if (ret)250 goto out;251 add += mtd->erasesize;252 } while (add < end_add);253 254out:255 ow_disable(map);256 mutex_unlock(&lpdd2_nvm_mutex);257 return ret;258}259 260/*261 * verify presence of PFOW string262 */263static 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(&lpdd2_nvm_mutex);291 292 return found;293}294 295/*296 * lpddr2_nvm driver read method297 */298static 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(&lpdd2_nvm_mutex);310 return 0;311}312 313/*314 * lpddr2_nvm driver write method315 */316static 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 my_data, add, 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 0x00, add, current_len, write_buf + tot_len);354 if (ret)355 goto out;356 357 add += current_len;358 tot_len += current_len;359 }360 }361 362out:363 *retlen = tot_len;364 ow_disable(map);365 mutex_unlock(&lpdd2_nvm_mutex);366 return ret;367}368 369/*370 * lpddr2_nvm driver erase method371 */372static int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr)373{374 return lpddr2_nvm_do_block_op(mtd, instr->addr, instr->len,375 LPDDR2_NVM_ERASE);376}377 378/*379 * lpddr2_nvm driver unlock method380 */381static 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 method389 */390static 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 396static 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 method409 */410static 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(&pdev->dev, 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(&pdev->dev, sizeof(*map), GFP_KERNEL);426 if (!map)427 return -ENOMEM;428 429 mtd = devm_kzalloc(&pdev->dev, 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(&pdev->dev, add_range),441 .name = pdev->dev.init_name,442 .phys = add_range->start,443 .size = resource_size(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(map->virt))450 return PTR_ERR(map->virt);451 452 simple_map_init(map); /* fill with default methods */453 454 pcm_data->ctl_regs = devm_platform_ioremap_resource(pdev, 1);455 if (IS_ERR(pcm_data->ctl_regs))456 return PTR_ERR(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(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 method478 */479static 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 */485static 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 493module_platform_driver(lpddr2_nvm_drv);494MODULE_LICENSE("GPL");495MODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>");496MODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories");497