162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * LPDDR2-NVM MTD driver. This module provides read, write, erase, lock/unlock
462306a36Sopenharmony_ci * support for LPDDR2-NVM PCM memories
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright © 2012 Micron Technology, Inc.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Vincenzo Aliberti <vincenzo.aliberti@gmail.com>
962306a36Sopenharmony_ci * Domenico Manna <domenico.manna@gmail.com>
1062306a36Sopenharmony_ci * Many thanks to Andrea Vigilante for initial enabling
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/io.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/kernel.h>
1962306a36Sopenharmony_ci#include <linux/mtd/map.h>
2062306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
2162306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/platform_device.h>
2462306a36Sopenharmony_ci#include <linux/ioport.h>
2562306a36Sopenharmony_ci#include <linux/err.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* Parameters */
2862306a36Sopenharmony_ci#define ERASE_BLOCKSIZE			(0x00020000/2)	/* in Word */
2962306a36Sopenharmony_ci#define WRITE_BUFFSIZE			(0x00000400/2)	/* in Word */
3062306a36Sopenharmony_ci#define OW_BASE_ADDRESS			0x00000000	/* OW offset */
3162306a36Sopenharmony_ci#define BUS_WIDTH			0x00000020	/* x32 devices */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/* PFOW symbols address offset */
3462306a36Sopenharmony_ci#define PFOW_QUERY_STRING_P		(0x0000/2)	/* in Word */
3562306a36Sopenharmony_ci#define PFOW_QUERY_STRING_F		(0x0002/2)	/* in Word */
3662306a36Sopenharmony_ci#define PFOW_QUERY_STRING_O		(0x0004/2)	/* in Word */
3762306a36Sopenharmony_ci#define PFOW_QUERY_STRING_W		(0x0006/2)	/* in Word */
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* OW registers address */
4062306a36Sopenharmony_ci#define CMD_CODE_OFS			(0x0080/2)	/* in Word */
4162306a36Sopenharmony_ci#define CMD_DATA_OFS			(0x0084/2)	/* in Word */
4262306a36Sopenharmony_ci#define CMD_ADD_L_OFS			(0x0088/2)	/* in Word */
4362306a36Sopenharmony_ci#define CMD_ADD_H_OFS			(0x008A/2)	/* in Word */
4462306a36Sopenharmony_ci#define MPR_L_OFS			(0x0090/2)	/* in Word */
4562306a36Sopenharmony_ci#define MPR_H_OFS			(0x0092/2)	/* in Word */
4662306a36Sopenharmony_ci#define CMD_EXEC_OFS			(0x00C0/2)	/* in Word */
4762306a36Sopenharmony_ci#define STATUS_REG_OFS			(0x00CC/2)	/* in Word */
4862306a36Sopenharmony_ci#define PRG_BUFFER_OFS			(0x0010/2)	/* in Word */
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/* Datamask */
5162306a36Sopenharmony_ci#define MR_CFGMASK			0x8000
5262306a36Sopenharmony_ci#define SR_OK_DATAMASK			0x0080
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/* LPDDR2-NVM Commands */
5562306a36Sopenharmony_ci#define LPDDR2_NVM_LOCK			0x0061
5662306a36Sopenharmony_ci#define LPDDR2_NVM_UNLOCK		0x0062
5762306a36Sopenharmony_ci#define LPDDR2_NVM_SW_PROGRAM		0x0041
5862306a36Sopenharmony_ci#define LPDDR2_NVM_SW_OVERWRITE		0x0042
5962306a36Sopenharmony_ci#define LPDDR2_NVM_BUF_PROGRAM		0x00E9
6062306a36Sopenharmony_ci#define LPDDR2_NVM_BUF_OVERWRITE	0x00EA
6162306a36Sopenharmony_ci#define LPDDR2_NVM_ERASE		0x0020
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/* LPDDR2-NVM Registers offset */
6462306a36Sopenharmony_ci#define LPDDR2_MODE_REG_DATA		0x0040
6562306a36Sopenharmony_ci#define LPDDR2_MODE_REG_CFG		0x0050
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/*
6862306a36Sopenharmony_ci * Internal Type Definitions
6962306a36Sopenharmony_ci * pcm_int_data contains memory controller details:
7062306a36Sopenharmony_ci * @reg_data : LPDDR2_MODE_REG_DATA register address after remapping
7162306a36Sopenharmony_ci * @reg_cfg  : LPDDR2_MODE_REG_CFG register address after remapping
7262306a36Sopenharmony_ci * &bus_width: memory bus-width (eg: x16 2 Bytes, x32 4 Bytes)
7362306a36Sopenharmony_ci */
7462306a36Sopenharmony_cistruct pcm_int_data {
7562306a36Sopenharmony_ci	void __iomem *ctl_regs;
7662306a36Sopenharmony_ci	int bus_width;
7762306a36Sopenharmony_ci};
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic DEFINE_MUTEX(lpdd2_nvm_mutex);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci/*
8262306a36Sopenharmony_ci * Build a map_word starting from an u_long
8362306a36Sopenharmony_ci */
8462306a36Sopenharmony_cistatic inline map_word build_map_word(u_long myword)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	map_word val = { {0} };
8762306a36Sopenharmony_ci	val.x[0] = myword;
8862306a36Sopenharmony_ci	return val;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/*
9262306a36Sopenharmony_ci * Build Mode Register Configuration DataMask based on device bus-width
9362306a36Sopenharmony_ci */
9462306a36Sopenharmony_cistatic inline u_int build_mr_cfgmask(u_int bus_width)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	u_int val = MR_CFGMASK;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (bus_width == 0x0004)		/* x32 device */
9962306a36Sopenharmony_ci		val = val << 16;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	return val;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci/*
10562306a36Sopenharmony_ci * Build Status Register OK DataMask based on device bus-width
10662306a36Sopenharmony_ci */
10762306a36Sopenharmony_cistatic inline u_int build_sr_ok_datamask(u_int bus_width)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	u_int val = SR_OK_DATAMASK;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (bus_width == 0x0004)		/* x32 device */
11262306a36Sopenharmony_ci		val = (val << 16)+val;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	return val;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci/*
11862306a36Sopenharmony_ci * Evaluates Overlay Window Control Registers address
11962306a36Sopenharmony_ci */
12062306a36Sopenharmony_cistatic inline u_long ow_reg_add(struct map_info *map, u_long offset)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	u_long val = 0;
12362306a36Sopenharmony_ci	struct pcm_int_data *pcm_data = map->fldrv_priv;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	val = map->pfow_base + offset*pcm_data->bus_width;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return val;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci/*
13162306a36Sopenharmony_ci * Enable lpddr2-nvm Overlay Window
13262306a36Sopenharmony_ci * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
13362306a36Sopenharmony_ci * used by device commands as well as uservisible resources like Device Status
13462306a36Sopenharmony_ci * Register, Device ID, etc
13562306a36Sopenharmony_ci */
13662306a36Sopenharmony_cistatic inline void ow_enable(struct map_info *map)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct pcm_int_data *pcm_data = map->fldrv_priv;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
14162306a36Sopenharmony_ci		pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
14262306a36Sopenharmony_ci	writel_relaxed(0x01, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci/*
14662306a36Sopenharmony_ci * Disable lpddr2-nvm Overlay Window
14762306a36Sopenharmony_ci * Overlay Window is a memory mapped area containing all LPDDR2-NVM registers
14862306a36Sopenharmony_ci * used by device commands as well as uservisible resources like Device Status
14962306a36Sopenharmony_ci * Register, Device ID, etc
15062306a36Sopenharmony_ci */
15162306a36Sopenharmony_cistatic inline void ow_disable(struct map_info *map)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct pcm_int_data *pcm_data = map->fldrv_priv;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	writel_relaxed(build_mr_cfgmask(pcm_data->bus_width) | 0x18,
15662306a36Sopenharmony_ci		pcm_data->ctl_regs + LPDDR2_MODE_REG_CFG);
15762306a36Sopenharmony_ci	writel_relaxed(0x02, pcm_data->ctl_regs + LPDDR2_MODE_REG_DATA);
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci/*
16162306a36Sopenharmony_ci * Execute lpddr2-nvm operations
16262306a36Sopenharmony_ci */
16362306a36Sopenharmony_cistatic int lpddr2_nvm_do_op(struct map_info *map, u_long cmd_code,
16462306a36Sopenharmony_ci	u_long cmd_data, u_long cmd_add, u_long cmd_mpr, u_char *buf)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	map_word add_l = { {0} }, add_h = { {0} }, mpr_l = { {0} },
16762306a36Sopenharmony_ci		mpr_h = { {0} }, data_l = { {0} }, cmd = { {0} },
16862306a36Sopenharmony_ci		exec_cmd = { {0} }, sr;
16962306a36Sopenharmony_ci	map_word data_h = { {0} };	/* only for 2x x16 devices stacked */
17062306a36Sopenharmony_ci	u_long i, status_reg, prg_buff_ofs;
17162306a36Sopenharmony_ci	struct pcm_int_data *pcm_data = map->fldrv_priv;
17262306a36Sopenharmony_ci	u_int sr_ok_datamask = build_sr_ok_datamask(pcm_data->bus_width);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	/* Builds low and high words for OW Control Registers */
17562306a36Sopenharmony_ci	add_l.x[0]	= cmd_add & 0x0000FFFF;
17662306a36Sopenharmony_ci	add_h.x[0]	= (cmd_add >> 16) & 0x0000FFFF;
17762306a36Sopenharmony_ci	mpr_l.x[0]	= cmd_mpr & 0x0000FFFF;
17862306a36Sopenharmony_ci	mpr_h.x[0]	= (cmd_mpr >> 16) & 0x0000FFFF;
17962306a36Sopenharmony_ci	cmd.x[0]	= cmd_code & 0x0000FFFF;
18062306a36Sopenharmony_ci	exec_cmd.x[0]	= 0x0001;
18162306a36Sopenharmony_ci	data_l.x[0]	= cmd_data & 0x0000FFFF;
18262306a36Sopenharmony_ci	data_h.x[0]	= (cmd_data >> 16) & 0x0000FFFF; /* only for 2x x16 */
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* Set Overlay Window Control Registers */
18562306a36Sopenharmony_ci	map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS));
18662306a36Sopenharmony_ci	map_write(map, data_l, ow_reg_add(map, CMD_DATA_OFS));
18762306a36Sopenharmony_ci	map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS));
18862306a36Sopenharmony_ci	map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS));
18962306a36Sopenharmony_ci	map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS));
19062306a36Sopenharmony_ci	map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS));
19162306a36Sopenharmony_ci	if (pcm_data->bus_width == 0x0004) {	/* 2x16 devices stacked */
19262306a36Sopenharmony_ci		map_write(map, cmd, ow_reg_add(map, CMD_CODE_OFS) + 2);
19362306a36Sopenharmony_ci		map_write(map, data_h, ow_reg_add(map, CMD_DATA_OFS) + 2);
19462306a36Sopenharmony_ci		map_write(map, add_l, ow_reg_add(map, CMD_ADD_L_OFS) + 2);
19562306a36Sopenharmony_ci		map_write(map, add_h, ow_reg_add(map, CMD_ADD_H_OFS) + 2);
19662306a36Sopenharmony_ci		map_write(map, mpr_l, ow_reg_add(map, MPR_L_OFS) + 2);
19762306a36Sopenharmony_ci		map_write(map, mpr_h, ow_reg_add(map, MPR_H_OFS) + 2);
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	/* Fill Program Buffer */
20162306a36Sopenharmony_ci	if ((cmd_code == LPDDR2_NVM_BUF_PROGRAM) ||
20262306a36Sopenharmony_ci		(cmd_code == LPDDR2_NVM_BUF_OVERWRITE)) {
20362306a36Sopenharmony_ci		prg_buff_ofs = (map_read(map,
20462306a36Sopenharmony_ci			ow_reg_add(map, PRG_BUFFER_OFS))).x[0];
20562306a36Sopenharmony_ci		for (i = 0; i < cmd_mpr; i++) {
20662306a36Sopenharmony_ci			map_write(map, build_map_word(buf[i]), map->pfow_base +
20762306a36Sopenharmony_ci			prg_buff_ofs + i);
20862306a36Sopenharmony_ci		}
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	/* Command Execute */
21262306a36Sopenharmony_ci	map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS));
21362306a36Sopenharmony_ci	if (pcm_data->bus_width == 0x0004)	/* 2x16 devices stacked */
21462306a36Sopenharmony_ci		map_write(map, exec_cmd, ow_reg_add(map, CMD_EXEC_OFS) + 2);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	/* Status Register Check */
21762306a36Sopenharmony_ci	do {
21862306a36Sopenharmony_ci		sr = map_read(map, ow_reg_add(map, STATUS_REG_OFS));
21962306a36Sopenharmony_ci		status_reg = sr.x[0];
22062306a36Sopenharmony_ci		if (pcm_data->bus_width == 0x0004) {/* 2x16 devices stacked */
22162306a36Sopenharmony_ci			sr = map_read(map, ow_reg_add(map,
22262306a36Sopenharmony_ci				STATUS_REG_OFS) + 2);
22362306a36Sopenharmony_ci			status_reg += sr.x[0] << 16;
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci	} while ((status_reg & sr_ok_datamask) != sr_ok_datamask);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	return (((status_reg & sr_ok_datamask) == sr_ok_datamask) ? 0 : -EIO);
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci/*
23162306a36Sopenharmony_ci * Execute lpddr2-nvm operations @ block level
23262306a36Sopenharmony_ci */
23362306a36Sopenharmony_cistatic int lpddr2_nvm_do_block_op(struct mtd_info *mtd, loff_t start_add,
23462306a36Sopenharmony_ci	uint64_t len, u_char block_op)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
23762306a36Sopenharmony_ci	u_long add, end_add;
23862306a36Sopenharmony_ci	int ret = 0;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	mutex_lock(&lpdd2_nvm_mutex);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	ow_enable(map);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	add = start_add;
24562306a36Sopenharmony_ci	end_add = add + len;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	do {
24862306a36Sopenharmony_ci		ret = lpddr2_nvm_do_op(map, block_op, 0x00, add, add, NULL);
24962306a36Sopenharmony_ci		if (ret)
25062306a36Sopenharmony_ci			goto out;
25162306a36Sopenharmony_ci		add += mtd->erasesize;
25262306a36Sopenharmony_ci	} while (add < end_add);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ciout:
25562306a36Sopenharmony_ci	ow_disable(map);
25662306a36Sopenharmony_ci	mutex_unlock(&lpdd2_nvm_mutex);
25762306a36Sopenharmony_ci	return ret;
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci/*
26162306a36Sopenharmony_ci * verify presence of PFOW string
26262306a36Sopenharmony_ci */
26362306a36Sopenharmony_cistatic int lpddr2_nvm_pfow_present(struct map_info *map)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	map_word pfow_val[4];
26662306a36Sopenharmony_ci	unsigned int found = 1;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	mutex_lock(&lpdd2_nvm_mutex);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	ow_enable(map);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/* Load string from array */
27362306a36Sopenharmony_ci	pfow_val[0] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_P));
27462306a36Sopenharmony_ci	pfow_val[1] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_F));
27562306a36Sopenharmony_ci	pfow_val[2] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_O));
27662306a36Sopenharmony_ci	pfow_val[3] = map_read(map, ow_reg_add(map, PFOW_QUERY_STRING_W));
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	/* Verify the string loaded vs expected */
27962306a36Sopenharmony_ci	if (!map_word_equal(map, build_map_word('P'), pfow_val[0]))
28062306a36Sopenharmony_ci		found = 0;
28162306a36Sopenharmony_ci	if (!map_word_equal(map, build_map_word('F'), pfow_val[1]))
28262306a36Sopenharmony_ci		found = 0;
28362306a36Sopenharmony_ci	if (!map_word_equal(map, build_map_word('O'), pfow_val[2]))
28462306a36Sopenharmony_ci		found = 0;
28562306a36Sopenharmony_ci	if (!map_word_equal(map, build_map_word('W'), pfow_val[3]))
28662306a36Sopenharmony_ci		found = 0;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	ow_disable(map);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	mutex_unlock(&lpdd2_nvm_mutex);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	return found;
29362306a36Sopenharmony_ci}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci/*
29662306a36Sopenharmony_ci * lpddr2_nvm driver read method
29762306a36Sopenharmony_ci */
29862306a36Sopenharmony_cistatic int lpddr2_nvm_read(struct mtd_info *mtd, loff_t start_add,
29962306a36Sopenharmony_ci				size_t len, size_t *retlen, u_char *buf)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	mutex_lock(&lpdd2_nvm_mutex);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	*retlen = len;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	map_copy_from(map, buf, start_add, *retlen);
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	mutex_unlock(&lpdd2_nvm_mutex);
31062306a36Sopenharmony_ci	return 0;
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci/*
31462306a36Sopenharmony_ci * lpddr2_nvm driver write method
31562306a36Sopenharmony_ci */
31662306a36Sopenharmony_cistatic int lpddr2_nvm_write(struct mtd_info *mtd, loff_t start_add,
31762306a36Sopenharmony_ci				size_t len, size_t *retlen, const u_char *buf)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
32062306a36Sopenharmony_ci	struct pcm_int_data *pcm_data = map->fldrv_priv;
32162306a36Sopenharmony_ci	u_long add, current_len, tot_len, target_len, my_data;
32262306a36Sopenharmony_ci	u_char *write_buf = (u_char *)buf;
32362306a36Sopenharmony_ci	int ret = 0;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	mutex_lock(&lpdd2_nvm_mutex);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	ow_enable(map);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	/* Set start value for the variables */
33062306a36Sopenharmony_ci	add = start_add;
33162306a36Sopenharmony_ci	target_len = len;
33262306a36Sopenharmony_ci	tot_len = 0;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	while (tot_len < target_len) {
33562306a36Sopenharmony_ci		if (!(IS_ALIGNED(add, mtd->writesize))) { /* do sw program */
33662306a36Sopenharmony_ci			my_data = write_buf[tot_len];
33762306a36Sopenharmony_ci			my_data += (write_buf[tot_len+1]) << 8;
33862306a36Sopenharmony_ci			if (pcm_data->bus_width == 0x0004) {/* 2x16 devices */
33962306a36Sopenharmony_ci				my_data += (write_buf[tot_len+2]) << 16;
34062306a36Sopenharmony_ci				my_data += (write_buf[tot_len+3]) << 24;
34162306a36Sopenharmony_ci			}
34262306a36Sopenharmony_ci			ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_SW_OVERWRITE,
34362306a36Sopenharmony_ci				my_data, add, 0x00, NULL);
34462306a36Sopenharmony_ci			if (ret)
34562306a36Sopenharmony_ci				goto out;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci			add += pcm_data->bus_width;
34862306a36Sopenharmony_ci			tot_len += pcm_data->bus_width;
34962306a36Sopenharmony_ci		} else {		/* do buffer program */
35062306a36Sopenharmony_ci			current_len = min(target_len - tot_len,
35162306a36Sopenharmony_ci				(u_long) mtd->writesize);
35262306a36Sopenharmony_ci			ret = lpddr2_nvm_do_op(map, LPDDR2_NVM_BUF_OVERWRITE,
35362306a36Sopenharmony_ci				0x00, add, current_len, write_buf + tot_len);
35462306a36Sopenharmony_ci			if (ret)
35562306a36Sopenharmony_ci				goto out;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci			add += current_len;
35862306a36Sopenharmony_ci			tot_len += current_len;
35962306a36Sopenharmony_ci		}
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ciout:
36362306a36Sopenharmony_ci	*retlen = tot_len;
36462306a36Sopenharmony_ci	ow_disable(map);
36562306a36Sopenharmony_ci	mutex_unlock(&lpdd2_nvm_mutex);
36662306a36Sopenharmony_ci	return ret;
36762306a36Sopenharmony_ci}
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci/*
37062306a36Sopenharmony_ci * lpddr2_nvm driver erase method
37162306a36Sopenharmony_ci */
37262306a36Sopenharmony_cistatic int lpddr2_nvm_erase(struct mtd_info *mtd, struct erase_info *instr)
37362306a36Sopenharmony_ci{
37462306a36Sopenharmony_ci	return lpddr2_nvm_do_block_op(mtd, instr->addr, instr->len,
37562306a36Sopenharmony_ci				      LPDDR2_NVM_ERASE);
37662306a36Sopenharmony_ci}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci/*
37962306a36Sopenharmony_ci * lpddr2_nvm driver unlock method
38062306a36Sopenharmony_ci */
38162306a36Sopenharmony_cistatic int lpddr2_nvm_unlock(struct mtd_info *mtd, loff_t start_add,
38262306a36Sopenharmony_ci	uint64_t len)
38362306a36Sopenharmony_ci{
38462306a36Sopenharmony_ci	return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_UNLOCK);
38562306a36Sopenharmony_ci}
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci/*
38862306a36Sopenharmony_ci * lpddr2_nvm driver lock method
38962306a36Sopenharmony_ci */
39062306a36Sopenharmony_cistatic int lpddr2_nvm_lock(struct mtd_info *mtd, loff_t start_add,
39162306a36Sopenharmony_ci	uint64_t len)
39262306a36Sopenharmony_ci{
39362306a36Sopenharmony_ci	return lpddr2_nvm_do_block_op(mtd, start_add, len, LPDDR2_NVM_LOCK);
39462306a36Sopenharmony_ci}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cistatic const struct mtd_info lpddr2_nvm_mtd_info = {
39762306a36Sopenharmony_ci	.type		= MTD_RAM,
39862306a36Sopenharmony_ci	.writesize	= 1,
39962306a36Sopenharmony_ci	.flags		= (MTD_CAP_NVRAM | MTD_POWERUP_LOCK),
40062306a36Sopenharmony_ci	._read		= lpddr2_nvm_read,
40162306a36Sopenharmony_ci	._write		= lpddr2_nvm_write,
40262306a36Sopenharmony_ci	._erase		= lpddr2_nvm_erase,
40362306a36Sopenharmony_ci	._unlock	= lpddr2_nvm_unlock,
40462306a36Sopenharmony_ci	._lock		= lpddr2_nvm_lock,
40562306a36Sopenharmony_ci};
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci/*
40862306a36Sopenharmony_ci * lpddr2_nvm driver probe method
40962306a36Sopenharmony_ci */
41062306a36Sopenharmony_cistatic int lpddr2_nvm_probe(struct platform_device *pdev)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	struct map_info *map;
41362306a36Sopenharmony_ci	struct mtd_info *mtd;
41462306a36Sopenharmony_ci	struct resource *add_range;
41562306a36Sopenharmony_ci	struct pcm_int_data *pcm_data;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	/* Allocate memory control_regs data structures */
41862306a36Sopenharmony_ci	pcm_data = devm_kzalloc(&pdev->dev, sizeof(*pcm_data), GFP_KERNEL);
41962306a36Sopenharmony_ci	if (!pcm_data)
42062306a36Sopenharmony_ci		return -ENOMEM;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	pcm_data->bus_width = BUS_WIDTH;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	/* Allocate memory for map_info & mtd_info data structures */
42562306a36Sopenharmony_ci	map = devm_kzalloc(&pdev->dev, sizeof(*map), GFP_KERNEL);
42662306a36Sopenharmony_ci	if (!map)
42762306a36Sopenharmony_ci		return -ENOMEM;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	mtd = devm_kzalloc(&pdev->dev, sizeof(*mtd), GFP_KERNEL);
43062306a36Sopenharmony_ci	if (!mtd)
43162306a36Sopenharmony_ci		return -ENOMEM;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	/* lpddr2_nvm address range */
43462306a36Sopenharmony_ci	add_range = platform_get_resource(pdev, IORESOURCE_MEM, 0);
43562306a36Sopenharmony_ci	if (!add_range)
43662306a36Sopenharmony_ci		return -ENODEV;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	/* Populate map_info data structure */
43962306a36Sopenharmony_ci	*map = (struct map_info) {
44062306a36Sopenharmony_ci		.virt		= devm_ioremap_resource(&pdev->dev, add_range),
44162306a36Sopenharmony_ci		.name		= pdev->dev.init_name,
44262306a36Sopenharmony_ci		.phys		= add_range->start,
44362306a36Sopenharmony_ci		.size		= resource_size(add_range),
44462306a36Sopenharmony_ci		.bankwidth	= pcm_data->bus_width / 2,
44562306a36Sopenharmony_ci		.pfow_base	= OW_BASE_ADDRESS,
44662306a36Sopenharmony_ci		.fldrv_priv	= pcm_data,
44762306a36Sopenharmony_ci	};
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	if (IS_ERR(map->virt))
45062306a36Sopenharmony_ci		return PTR_ERR(map->virt);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	simple_map_init(map);	/* fill with default methods */
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	pcm_data->ctl_regs = devm_platform_ioremap_resource(pdev, 1);
45562306a36Sopenharmony_ci	if (IS_ERR(pcm_data->ctl_regs))
45662306a36Sopenharmony_ci		return PTR_ERR(pcm_data->ctl_regs);
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	/* Populate mtd_info data structure */
45962306a36Sopenharmony_ci	*mtd = lpddr2_nvm_mtd_info;
46062306a36Sopenharmony_ci	mtd->dev.parent		= &pdev->dev;
46162306a36Sopenharmony_ci	mtd->name		= pdev->dev.init_name;
46262306a36Sopenharmony_ci	mtd->priv		= map;
46362306a36Sopenharmony_ci	mtd->size		= resource_size(add_range);
46462306a36Sopenharmony_ci	mtd->erasesize		= ERASE_BLOCKSIZE * pcm_data->bus_width;
46562306a36Sopenharmony_ci	mtd->writebufsize	= WRITE_BUFFSIZE * pcm_data->bus_width;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	/* Verify the presence of the device looking for PFOW string */
46862306a36Sopenharmony_ci	if (!lpddr2_nvm_pfow_present(map)) {
46962306a36Sopenharmony_ci		pr_err("device not recognized\n");
47062306a36Sopenharmony_ci		return -EINVAL;
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci	/* Parse partitions and register the MTD device */
47362306a36Sopenharmony_ci	return mtd_device_register(mtd, NULL, 0);
47462306a36Sopenharmony_ci}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci/*
47762306a36Sopenharmony_ci * lpddr2_nvm driver remove method
47862306a36Sopenharmony_ci */
47962306a36Sopenharmony_cistatic int lpddr2_nvm_remove(struct platform_device *pdev)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	WARN_ON(mtd_device_unregister(dev_get_drvdata(&pdev->dev)));
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	return 0;
48462306a36Sopenharmony_ci}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci/* Initialize platform_driver data structure for lpddr2_nvm */
48762306a36Sopenharmony_cistatic struct platform_driver lpddr2_nvm_drv = {
48862306a36Sopenharmony_ci	.driver		= {
48962306a36Sopenharmony_ci		.name	= "lpddr2_nvm",
49062306a36Sopenharmony_ci	},
49162306a36Sopenharmony_ci	.probe		= lpddr2_nvm_probe,
49262306a36Sopenharmony_ci	.remove		= lpddr2_nvm_remove,
49362306a36Sopenharmony_ci};
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_cimodule_platform_driver(lpddr2_nvm_drv);
49662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
49762306a36Sopenharmony_ciMODULE_AUTHOR("Vincenzo Aliberti <vincenzo.aliberti@gmail.com>");
49862306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD driver for LPDDR2-NVM PCM memories");
499