162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * LPDDR flash memory device operations. This module provides read, write,
462306a36Sopenharmony_ci * erase, lock/unlock support for LPDDR flash memories
562306a36Sopenharmony_ci * (C) 2008 Korolev Alexey <akorolev@infradead.org>
662306a36Sopenharmony_ci * (C) 2008 Vasiliy Leonenko <vasiliy.leonenko@gmail.com>
762306a36Sopenharmony_ci * Many thanks to Roman Borisov for initial enabling
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * TODO:
1062306a36Sopenharmony_ci * Implement VPP management
1162306a36Sopenharmony_ci * Implement XIP support
1262306a36Sopenharmony_ci * Implement OTP support
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci#include <linux/mtd/pfow.h>
1562306a36Sopenharmony_ci#include <linux/mtd/qinfo.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len,
2062306a36Sopenharmony_ci					size_t *retlen, u_char *buf);
2162306a36Sopenharmony_cistatic int lpddr_write_buffers(struct mtd_info *mtd, loff_t to,
2262306a36Sopenharmony_ci				size_t len, size_t *retlen, const u_char *buf);
2362306a36Sopenharmony_cistatic int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs,
2462306a36Sopenharmony_ci				unsigned long count, loff_t to, size_t *retlen);
2562306a36Sopenharmony_cistatic int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr);
2662306a36Sopenharmony_cistatic int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
2762306a36Sopenharmony_cistatic int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
2862306a36Sopenharmony_cistatic int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
2962306a36Sopenharmony_ci			size_t *retlen, void **mtdbuf, resource_size_t *phys);
3062306a36Sopenharmony_cistatic int lpddr_unpoint(struct mtd_info *mtd, loff_t adr, size_t len);
3162306a36Sopenharmony_cistatic int get_chip(struct map_info *map, struct flchip *chip, int mode);
3262306a36Sopenharmony_cistatic int chip_ready(struct map_info *map, struct flchip *chip, int mode);
3362306a36Sopenharmony_cistatic void put_chip(struct map_info *map, struct flchip *chip);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistruct mtd_info *lpddr_cmdset(struct map_info *map)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
3862306a36Sopenharmony_ci	struct flchip_shared *shared;
3962306a36Sopenharmony_ci	struct flchip *chip;
4062306a36Sopenharmony_ci	struct mtd_info *mtd;
4162306a36Sopenharmony_ci	int numchips;
4262306a36Sopenharmony_ci	int i, j;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
4562306a36Sopenharmony_ci	if (!mtd)
4662306a36Sopenharmony_ci		return NULL;
4762306a36Sopenharmony_ci	mtd->priv = map;
4862306a36Sopenharmony_ci	mtd->type = MTD_NORFLASH;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	/* Fill in the default mtd operations */
5162306a36Sopenharmony_ci	mtd->_read = lpddr_read;
5262306a36Sopenharmony_ci	mtd->type = MTD_NORFLASH;
5362306a36Sopenharmony_ci	mtd->flags = MTD_CAP_NORFLASH;
5462306a36Sopenharmony_ci	mtd->flags &= ~MTD_BIT_WRITEABLE;
5562306a36Sopenharmony_ci	mtd->_erase = lpddr_erase;
5662306a36Sopenharmony_ci	mtd->_write = lpddr_write_buffers;
5762306a36Sopenharmony_ci	mtd->_writev = lpddr_writev;
5862306a36Sopenharmony_ci	mtd->_lock = lpddr_lock;
5962306a36Sopenharmony_ci	mtd->_unlock = lpddr_unlock;
6062306a36Sopenharmony_ci	if (map_is_linear(map)) {
6162306a36Sopenharmony_ci		mtd->_point = lpddr_point;
6262306a36Sopenharmony_ci		mtd->_unpoint = lpddr_unpoint;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci	mtd->size = 1 << lpddr->qinfo->DevSizeShift;
6562306a36Sopenharmony_ci	mtd->erasesize = 1 << lpddr->qinfo->UniformBlockSizeShift;
6662306a36Sopenharmony_ci	mtd->writesize = 1 << lpddr->qinfo->BufSizeShift;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	shared = kmalloc_array(lpddr->numchips, sizeof(struct flchip_shared),
6962306a36Sopenharmony_ci						GFP_KERNEL);
7062306a36Sopenharmony_ci	if (!shared) {
7162306a36Sopenharmony_ci		kfree(mtd);
7262306a36Sopenharmony_ci		return NULL;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	chip = &lpddr->chips[0];
7662306a36Sopenharmony_ci	numchips = lpddr->numchips / lpddr->qinfo->HWPartsNum;
7762306a36Sopenharmony_ci	for (i = 0; i < numchips; i++) {
7862306a36Sopenharmony_ci		shared[i].writing = shared[i].erasing = NULL;
7962306a36Sopenharmony_ci		mutex_init(&shared[i].lock);
8062306a36Sopenharmony_ci		for (j = 0; j < lpddr->qinfo->HWPartsNum; j++) {
8162306a36Sopenharmony_ci			*chip = lpddr->chips[i];
8262306a36Sopenharmony_ci			chip->start += j << lpddr->chipshift;
8362306a36Sopenharmony_ci			chip->oldstate = chip->state = FL_READY;
8462306a36Sopenharmony_ci			chip->priv = &shared[i];
8562306a36Sopenharmony_ci			/* those should be reset too since
8662306a36Sopenharmony_ci			   they create memory references. */
8762306a36Sopenharmony_ci			init_waitqueue_head(&chip->wq);
8862306a36Sopenharmony_ci			mutex_init(&chip->mutex);
8962306a36Sopenharmony_ci			chip++;
9062306a36Sopenharmony_ci		}
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	return mtd;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ciEXPORT_SYMBOL(lpddr_cmdset);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic void print_drs_error(unsigned int dsr)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	int prog_status = (dsr & DSR_RPS) >> 8;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (!(dsr & DSR_AVAILABLE))
10262306a36Sopenharmony_ci		pr_notice("DSR.15: (0) Device not Available\n");
10362306a36Sopenharmony_ci	if ((prog_status & 0x03) == 0x03)
10462306a36Sopenharmony_ci		pr_notice("DSR.9,8: (11) Attempt to program invalid half with 41h command\n");
10562306a36Sopenharmony_ci	else if (prog_status & 0x02)
10662306a36Sopenharmony_ci		pr_notice("DSR.9,8: (10) Object Mode Program attempt in region with Control Mode data\n");
10762306a36Sopenharmony_ci	else if (prog_status &  0x01)
10862306a36Sopenharmony_ci		pr_notice("DSR.9,8: (01) Program attempt in region with Object Mode data\n");
10962306a36Sopenharmony_ci	if (!(dsr & DSR_READY_STATUS))
11062306a36Sopenharmony_ci		pr_notice("DSR.7: (0) Device is Busy\n");
11162306a36Sopenharmony_ci	if (dsr & DSR_ESS)
11262306a36Sopenharmony_ci		pr_notice("DSR.6: (1) Erase Suspended\n");
11362306a36Sopenharmony_ci	if (dsr & DSR_ERASE_STATUS)
11462306a36Sopenharmony_ci		pr_notice("DSR.5: (1) Erase/Blank check error\n");
11562306a36Sopenharmony_ci	if (dsr & DSR_PROGRAM_STATUS)
11662306a36Sopenharmony_ci		pr_notice("DSR.4: (1) Program Error\n");
11762306a36Sopenharmony_ci	if (dsr & DSR_VPPS)
11862306a36Sopenharmony_ci		pr_notice("DSR.3: (1) Vpp low detect, operation aborted\n");
11962306a36Sopenharmony_ci	if (dsr & DSR_PSS)
12062306a36Sopenharmony_ci		pr_notice("DSR.2: (1) Program suspended\n");
12162306a36Sopenharmony_ci	if (dsr & DSR_DPS)
12262306a36Sopenharmony_ci		pr_notice("DSR.1: (1) Aborted Erase/Program attempt on locked block\n");
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic int wait_for_ready(struct map_info *map, struct flchip *chip,
12662306a36Sopenharmony_ci		unsigned int chip_op_time)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	unsigned int timeo, reset_timeo, sleep_time;
12962306a36Sopenharmony_ci	unsigned int dsr;
13062306a36Sopenharmony_ci	flstate_t chip_state = chip->state;
13162306a36Sopenharmony_ci	int ret = 0;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* set our timeout to 8 times the expected delay */
13462306a36Sopenharmony_ci	timeo = chip_op_time * 8;
13562306a36Sopenharmony_ci	if (!timeo)
13662306a36Sopenharmony_ci		timeo = 500000;
13762306a36Sopenharmony_ci	reset_timeo = timeo;
13862306a36Sopenharmony_ci	sleep_time = chip_op_time / 2;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	for (;;) {
14162306a36Sopenharmony_ci		dsr = CMDVAL(map_read(map, map->pfow_base + PFOW_DSR));
14262306a36Sopenharmony_ci		if (dsr & DSR_READY_STATUS)
14362306a36Sopenharmony_ci			break;
14462306a36Sopenharmony_ci		if (!timeo) {
14562306a36Sopenharmony_ci			printk(KERN_ERR "%s: Flash timeout error state %d \n",
14662306a36Sopenharmony_ci							map->name, chip_state);
14762306a36Sopenharmony_ci			ret = -ETIME;
14862306a36Sopenharmony_ci			break;
14962306a36Sopenharmony_ci		}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci		/* OK Still waiting. Drop the lock, wait a while and retry. */
15262306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
15362306a36Sopenharmony_ci		if (sleep_time >= 1000000/HZ) {
15462306a36Sopenharmony_ci			/*
15562306a36Sopenharmony_ci			 * Half of the normal delay still remaining
15662306a36Sopenharmony_ci			 * can be performed with a sleeping delay instead
15762306a36Sopenharmony_ci			 * of busy waiting.
15862306a36Sopenharmony_ci			 */
15962306a36Sopenharmony_ci			msleep(sleep_time/1000);
16062306a36Sopenharmony_ci			timeo -= sleep_time;
16162306a36Sopenharmony_ci			sleep_time = 1000000/HZ;
16262306a36Sopenharmony_ci		} else {
16362306a36Sopenharmony_ci			udelay(1);
16462306a36Sopenharmony_ci			cond_resched();
16562306a36Sopenharmony_ci			timeo--;
16662306a36Sopenharmony_ci		}
16762306a36Sopenharmony_ci		mutex_lock(&chip->mutex);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci		while (chip->state != chip_state) {
17062306a36Sopenharmony_ci			/* Someone's suspended the operation: sleep */
17162306a36Sopenharmony_ci			DECLARE_WAITQUEUE(wait, current);
17262306a36Sopenharmony_ci			set_current_state(TASK_UNINTERRUPTIBLE);
17362306a36Sopenharmony_ci			add_wait_queue(&chip->wq, &wait);
17462306a36Sopenharmony_ci			mutex_unlock(&chip->mutex);
17562306a36Sopenharmony_ci			schedule();
17662306a36Sopenharmony_ci			remove_wait_queue(&chip->wq, &wait);
17762306a36Sopenharmony_ci			mutex_lock(&chip->mutex);
17862306a36Sopenharmony_ci		}
17962306a36Sopenharmony_ci		if (chip->erase_suspended || chip->write_suspended)  {
18062306a36Sopenharmony_ci			/* Suspend has occurred while sleep: reset timeout */
18162306a36Sopenharmony_ci			timeo = reset_timeo;
18262306a36Sopenharmony_ci			chip->erase_suspended = chip->write_suspended = 0;
18362306a36Sopenharmony_ci		}
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci	/* check status for errors */
18662306a36Sopenharmony_ci	if (dsr & DSR_ERR) {
18762306a36Sopenharmony_ci		/* Clear DSR*/
18862306a36Sopenharmony_ci		map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR);
18962306a36Sopenharmony_ci		printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n",
19062306a36Sopenharmony_ci				map->name, dsr);
19162306a36Sopenharmony_ci		print_drs_error(dsr);
19262306a36Sopenharmony_ci		ret = -EIO;
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci	chip->state = FL_READY;
19562306a36Sopenharmony_ci	return ret;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic int get_chip(struct map_info *map, struct flchip *chip, int mode)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	int ret;
20162306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci retry:
20462306a36Sopenharmony_ci	if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING)
20562306a36Sopenharmony_ci		&& chip->state != FL_SYNCING) {
20662306a36Sopenharmony_ci		/*
20762306a36Sopenharmony_ci		 * OK. We have possibility for contension on the write/erase
20862306a36Sopenharmony_ci		 * operations which are global to the real chip and not per
20962306a36Sopenharmony_ci		 * partition.  So let's fight it over in the partition which
21062306a36Sopenharmony_ci		 * currently has authority on the operation.
21162306a36Sopenharmony_ci		 *
21262306a36Sopenharmony_ci		 * The rules are as follows:
21362306a36Sopenharmony_ci		 *
21462306a36Sopenharmony_ci		 * - any write operation must own shared->writing.
21562306a36Sopenharmony_ci		 *
21662306a36Sopenharmony_ci		 * - any erase operation must own _both_ shared->writing and
21762306a36Sopenharmony_ci		 *   shared->erasing.
21862306a36Sopenharmony_ci		 *
21962306a36Sopenharmony_ci		 * - contension arbitration is handled in the owner's context.
22062306a36Sopenharmony_ci		 *
22162306a36Sopenharmony_ci		 * The 'shared' struct can be read and/or written only when
22262306a36Sopenharmony_ci		 * its lock is taken.
22362306a36Sopenharmony_ci		 */
22462306a36Sopenharmony_ci		struct flchip_shared *shared = chip->priv;
22562306a36Sopenharmony_ci		struct flchip *contender;
22662306a36Sopenharmony_ci		mutex_lock(&shared->lock);
22762306a36Sopenharmony_ci		contender = shared->writing;
22862306a36Sopenharmony_ci		if (contender && contender != chip) {
22962306a36Sopenharmony_ci			/*
23062306a36Sopenharmony_ci			 * The engine to perform desired operation on this
23162306a36Sopenharmony_ci			 * partition is already in use by someone else.
23262306a36Sopenharmony_ci			 * Let's fight over it in the context of the chip
23362306a36Sopenharmony_ci			 * currently using it.  If it is possible to suspend,
23462306a36Sopenharmony_ci			 * that other partition will do just that, otherwise
23562306a36Sopenharmony_ci			 * it'll happily send us to sleep.  In any case, when
23662306a36Sopenharmony_ci			 * get_chip returns success we're clear to go ahead.
23762306a36Sopenharmony_ci			 */
23862306a36Sopenharmony_ci			ret = mutex_trylock(&contender->mutex);
23962306a36Sopenharmony_ci			mutex_unlock(&shared->lock);
24062306a36Sopenharmony_ci			if (!ret)
24162306a36Sopenharmony_ci				goto retry;
24262306a36Sopenharmony_ci			mutex_unlock(&chip->mutex);
24362306a36Sopenharmony_ci			ret = chip_ready(map, contender, mode);
24462306a36Sopenharmony_ci			mutex_lock(&chip->mutex);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci			if (ret == -EAGAIN) {
24762306a36Sopenharmony_ci				mutex_unlock(&contender->mutex);
24862306a36Sopenharmony_ci				goto retry;
24962306a36Sopenharmony_ci			}
25062306a36Sopenharmony_ci			if (ret) {
25162306a36Sopenharmony_ci				mutex_unlock(&contender->mutex);
25262306a36Sopenharmony_ci				return ret;
25362306a36Sopenharmony_ci			}
25462306a36Sopenharmony_ci			mutex_lock(&shared->lock);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci			/* We should not own chip if it is already in FL_SYNCING
25762306a36Sopenharmony_ci			 * state. Put contender and retry. */
25862306a36Sopenharmony_ci			if (chip->state == FL_SYNCING) {
25962306a36Sopenharmony_ci				put_chip(map, contender);
26062306a36Sopenharmony_ci				mutex_unlock(&contender->mutex);
26162306a36Sopenharmony_ci				goto retry;
26262306a36Sopenharmony_ci			}
26362306a36Sopenharmony_ci			mutex_unlock(&contender->mutex);
26462306a36Sopenharmony_ci		}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci		/* Check if we have suspended erase on this chip.
26762306a36Sopenharmony_ci		   Must sleep in such a case. */
26862306a36Sopenharmony_ci		if (mode == FL_ERASING && shared->erasing
26962306a36Sopenharmony_ci		    && shared->erasing->oldstate == FL_ERASING) {
27062306a36Sopenharmony_ci			mutex_unlock(&shared->lock);
27162306a36Sopenharmony_ci			set_current_state(TASK_UNINTERRUPTIBLE);
27262306a36Sopenharmony_ci			add_wait_queue(&chip->wq, &wait);
27362306a36Sopenharmony_ci			mutex_unlock(&chip->mutex);
27462306a36Sopenharmony_ci			schedule();
27562306a36Sopenharmony_ci			remove_wait_queue(&chip->wq, &wait);
27662306a36Sopenharmony_ci			mutex_lock(&chip->mutex);
27762306a36Sopenharmony_ci			goto retry;
27862306a36Sopenharmony_ci		}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci		/* We now own it */
28162306a36Sopenharmony_ci		shared->writing = chip;
28262306a36Sopenharmony_ci		if (mode == FL_ERASING)
28362306a36Sopenharmony_ci			shared->erasing = chip;
28462306a36Sopenharmony_ci		mutex_unlock(&shared->lock);
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	ret = chip_ready(map, chip, mode);
28862306a36Sopenharmony_ci	if (ret == -EAGAIN)
28962306a36Sopenharmony_ci		goto retry;
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	return ret;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic int chip_ready(struct map_info *map, struct flchip *chip, int mode)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
29762306a36Sopenharmony_ci	int ret = 0;
29862306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	/* Prevent setting state FL_SYNCING for chip in suspended state. */
30162306a36Sopenharmony_ci	if (FL_SYNCING == mode && FL_READY != chip->oldstate)
30262306a36Sopenharmony_ci		goto sleep;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	switch (chip->state) {
30562306a36Sopenharmony_ci	case FL_READY:
30662306a36Sopenharmony_ci	case FL_JEDEC_QUERY:
30762306a36Sopenharmony_ci		return 0;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	case FL_ERASING:
31062306a36Sopenharmony_ci		if (!lpddr->qinfo->SuspEraseSupp ||
31162306a36Sopenharmony_ci			!(mode == FL_READY || mode == FL_POINT))
31262306a36Sopenharmony_ci			goto sleep;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci		map_write(map, CMD(LPDDR_SUSPEND),
31562306a36Sopenharmony_ci			map->pfow_base + PFOW_PROGRAM_ERASE_SUSPEND);
31662306a36Sopenharmony_ci		chip->oldstate = FL_ERASING;
31762306a36Sopenharmony_ci		chip->state = FL_ERASE_SUSPENDING;
31862306a36Sopenharmony_ci		ret = wait_for_ready(map, chip, 0);
31962306a36Sopenharmony_ci		if (ret) {
32062306a36Sopenharmony_ci			/* Oops. something got wrong. */
32162306a36Sopenharmony_ci			/* Resume and pretend we weren't here.  */
32262306a36Sopenharmony_ci			put_chip(map, chip);
32362306a36Sopenharmony_ci			printk(KERN_ERR "%s: suspend operation failed."
32462306a36Sopenharmony_ci					"State may be wrong \n", map->name);
32562306a36Sopenharmony_ci			return -EIO;
32662306a36Sopenharmony_ci		}
32762306a36Sopenharmony_ci		chip->erase_suspended = 1;
32862306a36Sopenharmony_ci		chip->state = FL_READY;
32962306a36Sopenharmony_ci		return 0;
33062306a36Sopenharmony_ci		/* Erase suspend */
33162306a36Sopenharmony_ci	case FL_POINT:
33262306a36Sopenharmony_ci		/* Only if there's no operation suspended... */
33362306a36Sopenharmony_ci		if (mode == FL_READY && chip->oldstate == FL_READY)
33462306a36Sopenharmony_ci			return 0;
33562306a36Sopenharmony_ci		fallthrough;
33662306a36Sopenharmony_ci	default:
33762306a36Sopenharmony_cisleep:
33862306a36Sopenharmony_ci		set_current_state(TASK_UNINTERRUPTIBLE);
33962306a36Sopenharmony_ci		add_wait_queue(&chip->wq, &wait);
34062306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
34162306a36Sopenharmony_ci		schedule();
34262306a36Sopenharmony_ci		remove_wait_queue(&chip->wq, &wait);
34362306a36Sopenharmony_ci		mutex_lock(&chip->mutex);
34462306a36Sopenharmony_ci		return -EAGAIN;
34562306a36Sopenharmony_ci	}
34662306a36Sopenharmony_ci}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_cistatic void put_chip(struct map_info *map, struct flchip *chip)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	if (chip->priv) {
35162306a36Sopenharmony_ci		struct flchip_shared *shared = chip->priv;
35262306a36Sopenharmony_ci		mutex_lock(&shared->lock);
35362306a36Sopenharmony_ci		if (shared->writing == chip && chip->oldstate == FL_READY) {
35462306a36Sopenharmony_ci			/* We own the ability to write, but we're done */
35562306a36Sopenharmony_ci			shared->writing = shared->erasing;
35662306a36Sopenharmony_ci			if (shared->writing && shared->writing != chip) {
35762306a36Sopenharmony_ci				/* give back the ownership */
35862306a36Sopenharmony_ci				struct flchip *loaner = shared->writing;
35962306a36Sopenharmony_ci				mutex_lock(&loaner->mutex);
36062306a36Sopenharmony_ci				mutex_unlock(&shared->lock);
36162306a36Sopenharmony_ci				mutex_unlock(&chip->mutex);
36262306a36Sopenharmony_ci				put_chip(map, loaner);
36362306a36Sopenharmony_ci				mutex_lock(&chip->mutex);
36462306a36Sopenharmony_ci				mutex_unlock(&loaner->mutex);
36562306a36Sopenharmony_ci				wake_up(&chip->wq);
36662306a36Sopenharmony_ci				return;
36762306a36Sopenharmony_ci			}
36862306a36Sopenharmony_ci			shared->erasing = NULL;
36962306a36Sopenharmony_ci			shared->writing = NULL;
37062306a36Sopenharmony_ci		} else if (shared->erasing == chip && shared->writing != chip) {
37162306a36Sopenharmony_ci			/*
37262306a36Sopenharmony_ci			 * We own the ability to erase without the ability
37362306a36Sopenharmony_ci			 * to write, which means the erase was suspended
37462306a36Sopenharmony_ci			 * and some other partition is currently writing.
37562306a36Sopenharmony_ci			 * Don't let the switch below mess things up since
37662306a36Sopenharmony_ci			 * we don't have ownership to resume anything.
37762306a36Sopenharmony_ci			 */
37862306a36Sopenharmony_ci			mutex_unlock(&shared->lock);
37962306a36Sopenharmony_ci			wake_up(&chip->wq);
38062306a36Sopenharmony_ci			return;
38162306a36Sopenharmony_ci		}
38262306a36Sopenharmony_ci		mutex_unlock(&shared->lock);
38362306a36Sopenharmony_ci	}
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	switch (chip->oldstate) {
38662306a36Sopenharmony_ci	case FL_ERASING:
38762306a36Sopenharmony_ci		map_write(map, CMD(LPDDR_RESUME),
38862306a36Sopenharmony_ci				map->pfow_base + PFOW_COMMAND_CODE);
38962306a36Sopenharmony_ci		map_write(map, CMD(LPDDR_START_EXECUTION),
39062306a36Sopenharmony_ci				map->pfow_base + PFOW_COMMAND_EXECUTE);
39162306a36Sopenharmony_ci		chip->oldstate = FL_READY;
39262306a36Sopenharmony_ci		chip->state = FL_ERASING;
39362306a36Sopenharmony_ci		break;
39462306a36Sopenharmony_ci	case FL_READY:
39562306a36Sopenharmony_ci		break;
39662306a36Sopenharmony_ci	default:
39762306a36Sopenharmony_ci		printk(KERN_ERR "%s: put_chip() called with oldstate %d!\n",
39862306a36Sopenharmony_ci				map->name, chip->oldstate);
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci	wake_up(&chip->wq);
40162306a36Sopenharmony_ci}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_cistatic int do_write_buffer(struct map_info *map, struct flchip *chip,
40462306a36Sopenharmony_ci			unsigned long adr, const struct kvec **pvec,
40562306a36Sopenharmony_ci			unsigned long *pvec_seek, int len)
40662306a36Sopenharmony_ci{
40762306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
40862306a36Sopenharmony_ci	map_word datum;
40962306a36Sopenharmony_ci	int ret, wbufsize, word_gap;
41062306a36Sopenharmony_ci	const struct kvec *vec;
41162306a36Sopenharmony_ci	unsigned long vec_seek;
41262306a36Sopenharmony_ci	unsigned long prog_buf_ofs;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	wbufsize = 1 << lpddr->qinfo->BufSizeShift;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	mutex_lock(&chip->mutex);
41762306a36Sopenharmony_ci	ret = get_chip(map, chip, FL_WRITING);
41862306a36Sopenharmony_ci	if (ret) {
41962306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
42062306a36Sopenharmony_ci		return ret;
42162306a36Sopenharmony_ci	}
42262306a36Sopenharmony_ci	/* Figure out the number of words to write */
42362306a36Sopenharmony_ci	word_gap = (-adr & (map_bankwidth(map)-1));
42462306a36Sopenharmony_ci	if (word_gap) {
42562306a36Sopenharmony_ci		word_gap = map_bankwidth(map) - word_gap;
42662306a36Sopenharmony_ci		adr -= word_gap;
42762306a36Sopenharmony_ci		datum = map_word_ff(map);
42862306a36Sopenharmony_ci	}
42962306a36Sopenharmony_ci	/* Write data */
43062306a36Sopenharmony_ci	/* Get the program buffer offset from PFOW register data first*/
43162306a36Sopenharmony_ci	prog_buf_ofs = map->pfow_base + CMDVAL(map_read(map,
43262306a36Sopenharmony_ci				map->pfow_base + PFOW_PROGRAM_BUFFER_OFFSET));
43362306a36Sopenharmony_ci	vec = *pvec;
43462306a36Sopenharmony_ci	vec_seek = *pvec_seek;
43562306a36Sopenharmony_ci	do {
43662306a36Sopenharmony_ci		int n = map_bankwidth(map) - word_gap;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		if (n > vec->iov_len - vec_seek)
43962306a36Sopenharmony_ci			n = vec->iov_len - vec_seek;
44062306a36Sopenharmony_ci		if (n > len)
44162306a36Sopenharmony_ci			n = len;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci		if (!word_gap && (len < map_bankwidth(map)))
44462306a36Sopenharmony_ci			datum = map_word_ff(map);
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci		datum = map_word_load_partial(map, datum,
44762306a36Sopenharmony_ci				vec->iov_base + vec_seek, word_gap, n);
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci		len -= n;
45062306a36Sopenharmony_ci		word_gap += n;
45162306a36Sopenharmony_ci		if (!len || word_gap == map_bankwidth(map)) {
45262306a36Sopenharmony_ci			map_write(map, datum, prog_buf_ofs);
45362306a36Sopenharmony_ci			prog_buf_ofs += map_bankwidth(map);
45462306a36Sopenharmony_ci			word_gap = 0;
45562306a36Sopenharmony_ci		}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci		vec_seek += n;
45862306a36Sopenharmony_ci		if (vec_seek == vec->iov_len) {
45962306a36Sopenharmony_ci			vec++;
46062306a36Sopenharmony_ci			vec_seek = 0;
46162306a36Sopenharmony_ci		}
46262306a36Sopenharmony_ci	} while (len);
46362306a36Sopenharmony_ci	*pvec = vec;
46462306a36Sopenharmony_ci	*pvec_seek = vec_seek;
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	/* GO GO GO */
46762306a36Sopenharmony_ci	send_pfow_command(map, LPDDR_BUFF_PROGRAM, adr, wbufsize, NULL);
46862306a36Sopenharmony_ci	chip->state = FL_WRITING;
46962306a36Sopenharmony_ci	ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime));
47062306a36Sopenharmony_ci	if (ret)	{
47162306a36Sopenharmony_ci		printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n",
47262306a36Sopenharmony_ci			map->name, ret, adr);
47362306a36Sopenharmony_ci		goto out;
47462306a36Sopenharmony_ci	}
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci out:	put_chip(map, chip);
47762306a36Sopenharmony_ci	mutex_unlock(&chip->mutex);
47862306a36Sopenharmony_ci	return ret;
47962306a36Sopenharmony_ci}
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_cistatic int do_erase_oneblock(struct mtd_info *mtd, loff_t adr)
48262306a36Sopenharmony_ci{
48362306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
48462306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
48562306a36Sopenharmony_ci	int chipnum = adr >> lpddr->chipshift;
48662306a36Sopenharmony_ci	struct flchip *chip = &lpddr->chips[chipnum];
48762306a36Sopenharmony_ci	int ret;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	mutex_lock(&chip->mutex);
49062306a36Sopenharmony_ci	ret = get_chip(map, chip, FL_ERASING);
49162306a36Sopenharmony_ci	if (ret) {
49262306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
49362306a36Sopenharmony_ci		return ret;
49462306a36Sopenharmony_ci	}
49562306a36Sopenharmony_ci	send_pfow_command(map, LPDDR_BLOCK_ERASE, adr, 0, NULL);
49662306a36Sopenharmony_ci	chip->state = FL_ERASING;
49762306a36Sopenharmony_ci	ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->BlockEraseTime)*1000);
49862306a36Sopenharmony_ci	if (ret) {
49962306a36Sopenharmony_ci		printk(KERN_WARNING"%s Erase block error %d at : %llx\n",
50062306a36Sopenharmony_ci			map->name, ret, adr);
50162306a36Sopenharmony_ci		goto out;
50262306a36Sopenharmony_ci	}
50362306a36Sopenharmony_ci out:	put_chip(map, chip);
50462306a36Sopenharmony_ci	mutex_unlock(&chip->mutex);
50562306a36Sopenharmony_ci	return ret;
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic int lpddr_read(struct mtd_info *mtd, loff_t adr, size_t len,
50962306a36Sopenharmony_ci			size_t *retlen, u_char *buf)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
51262306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
51362306a36Sopenharmony_ci	int chipnum = adr >> lpddr->chipshift;
51462306a36Sopenharmony_ci	struct flchip *chip = &lpddr->chips[chipnum];
51562306a36Sopenharmony_ci	int ret = 0;
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	mutex_lock(&chip->mutex);
51862306a36Sopenharmony_ci	ret = get_chip(map, chip, FL_READY);
51962306a36Sopenharmony_ci	if (ret) {
52062306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
52162306a36Sopenharmony_ci		return ret;
52262306a36Sopenharmony_ci	}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	map_copy_from(map, buf, adr, len);
52562306a36Sopenharmony_ci	*retlen = len;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	put_chip(map, chip);
52862306a36Sopenharmony_ci	mutex_unlock(&chip->mutex);
52962306a36Sopenharmony_ci	return ret;
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_cistatic int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
53362306a36Sopenharmony_ci			size_t *retlen, void **mtdbuf, resource_size_t *phys)
53462306a36Sopenharmony_ci{
53562306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
53662306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
53762306a36Sopenharmony_ci	int chipnum = adr >> lpddr->chipshift;
53862306a36Sopenharmony_ci	unsigned long ofs, last_end = 0;
53962306a36Sopenharmony_ci	struct flchip *chip = &lpddr->chips[chipnum];
54062306a36Sopenharmony_ci	int ret = 0;
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	if (!map->virt)
54362306a36Sopenharmony_ci		return -EINVAL;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	/* ofs: offset within the first chip that the first read should start */
54662306a36Sopenharmony_ci	ofs = adr - (chipnum << lpddr->chipshift);
54762306a36Sopenharmony_ci	*mtdbuf = (void *)map->virt + chip->start + ofs;
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci	while (len) {
55062306a36Sopenharmony_ci		unsigned long thislen;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci		if (chipnum >= lpddr->numchips)
55362306a36Sopenharmony_ci			break;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci		/* We cannot point across chips that are virtually disjoint */
55662306a36Sopenharmony_ci		if (!last_end)
55762306a36Sopenharmony_ci			last_end = chip->start;
55862306a36Sopenharmony_ci		else if (chip->start != last_end)
55962306a36Sopenharmony_ci			break;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci		if ((len + ofs - 1) >> lpddr->chipshift)
56262306a36Sopenharmony_ci			thislen = (1<<lpddr->chipshift) - ofs;
56362306a36Sopenharmony_ci		else
56462306a36Sopenharmony_ci			thislen = len;
56562306a36Sopenharmony_ci		/* get the chip */
56662306a36Sopenharmony_ci		mutex_lock(&chip->mutex);
56762306a36Sopenharmony_ci		ret = get_chip(map, chip, FL_POINT);
56862306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
56962306a36Sopenharmony_ci		if (ret)
57062306a36Sopenharmony_ci			break;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci		chip->state = FL_POINT;
57362306a36Sopenharmony_ci		chip->ref_point_counter++;
57462306a36Sopenharmony_ci		*retlen += thislen;
57562306a36Sopenharmony_ci		len -= thislen;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci		ofs = 0;
57862306a36Sopenharmony_ci		last_end += 1 << lpddr->chipshift;
57962306a36Sopenharmony_ci		chipnum++;
58062306a36Sopenharmony_ci		chip = &lpddr->chips[chipnum];
58162306a36Sopenharmony_ci	}
58262306a36Sopenharmony_ci	return 0;
58362306a36Sopenharmony_ci}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_cistatic int lpddr_unpoint (struct mtd_info *mtd, loff_t adr, size_t len)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
58862306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
58962306a36Sopenharmony_ci	int chipnum = adr >> lpddr->chipshift, err = 0;
59062306a36Sopenharmony_ci	unsigned long ofs;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	/* ofs: offset within the first chip that the first read should start */
59362306a36Sopenharmony_ci	ofs = adr - (chipnum << lpddr->chipshift);
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	while (len) {
59662306a36Sopenharmony_ci		unsigned long thislen;
59762306a36Sopenharmony_ci		struct flchip *chip;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci		chip = &lpddr->chips[chipnum];
60062306a36Sopenharmony_ci		if (chipnum >= lpddr->numchips)
60162306a36Sopenharmony_ci			break;
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci		if ((len + ofs - 1) >> lpddr->chipshift)
60462306a36Sopenharmony_ci			thislen = (1<<lpddr->chipshift) - ofs;
60562306a36Sopenharmony_ci		else
60662306a36Sopenharmony_ci			thislen = len;
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci		mutex_lock(&chip->mutex);
60962306a36Sopenharmony_ci		if (chip->state == FL_POINT) {
61062306a36Sopenharmony_ci			chip->ref_point_counter--;
61162306a36Sopenharmony_ci			if (chip->ref_point_counter == 0)
61262306a36Sopenharmony_ci				chip->state = FL_READY;
61362306a36Sopenharmony_ci		} else {
61462306a36Sopenharmony_ci			printk(KERN_WARNING "%s: Warning: unpoint called on non"
61562306a36Sopenharmony_ci					"pointed region\n", map->name);
61662306a36Sopenharmony_ci			err = -EINVAL;
61762306a36Sopenharmony_ci		}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci		put_chip(map, chip);
62062306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci		len -= thislen;
62362306a36Sopenharmony_ci		ofs = 0;
62462306a36Sopenharmony_ci		chipnum++;
62562306a36Sopenharmony_ci	}
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	return err;
62862306a36Sopenharmony_ci}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic int lpddr_write_buffers(struct mtd_info *mtd, loff_t to, size_t len,
63162306a36Sopenharmony_ci				size_t *retlen, const u_char *buf)
63262306a36Sopenharmony_ci{
63362306a36Sopenharmony_ci	struct kvec vec;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	vec.iov_base = (void *) buf;
63662306a36Sopenharmony_ci	vec.iov_len = len;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	return lpddr_writev(mtd, &vec, 1, to, retlen);
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_cistatic int lpddr_writev(struct mtd_info *mtd, const struct kvec *vecs,
64362306a36Sopenharmony_ci				unsigned long count, loff_t to, size_t *retlen)
64462306a36Sopenharmony_ci{
64562306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
64662306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
64762306a36Sopenharmony_ci	int ret = 0;
64862306a36Sopenharmony_ci	int chipnum;
64962306a36Sopenharmony_ci	unsigned long ofs, vec_seek, i;
65062306a36Sopenharmony_ci	int wbufsize = 1 << lpddr->qinfo->BufSizeShift;
65162306a36Sopenharmony_ci	size_t len = 0;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	for (i = 0; i < count; i++)
65462306a36Sopenharmony_ci		len += vecs[i].iov_len;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	if (!len)
65762306a36Sopenharmony_ci		return 0;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	chipnum = to >> lpddr->chipshift;
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	ofs = to;
66262306a36Sopenharmony_ci	vec_seek = 0;
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci	do {
66562306a36Sopenharmony_ci		/* We must not cross write block boundaries */
66662306a36Sopenharmony_ci		int size = wbufsize - (ofs & (wbufsize-1));
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci		if (size > len)
66962306a36Sopenharmony_ci			size = len;
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci		ret = do_write_buffer(map, &lpddr->chips[chipnum],
67262306a36Sopenharmony_ci					  ofs, &vecs, &vec_seek, size);
67362306a36Sopenharmony_ci		if (ret)
67462306a36Sopenharmony_ci			return ret;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci		ofs += size;
67762306a36Sopenharmony_ci		(*retlen) += size;
67862306a36Sopenharmony_ci		len -= size;
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci		/* Be nice and reschedule with the chip in a usable
68162306a36Sopenharmony_ci		 * state for other processes */
68262306a36Sopenharmony_ci		cond_resched();
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	} while (len);
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	return 0;
68762306a36Sopenharmony_ci}
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_cistatic int lpddr_erase(struct mtd_info *mtd, struct erase_info *instr)
69062306a36Sopenharmony_ci{
69162306a36Sopenharmony_ci	unsigned long ofs, len;
69262306a36Sopenharmony_ci	int ret;
69362306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
69462306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
69562306a36Sopenharmony_ci	int size = 1 << lpddr->qinfo->UniformBlockSizeShift;
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	ofs = instr->addr;
69862306a36Sopenharmony_ci	len = instr->len;
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci	while (len > 0) {
70162306a36Sopenharmony_ci		ret = do_erase_oneblock(mtd, ofs);
70262306a36Sopenharmony_ci		if (ret)
70362306a36Sopenharmony_ci			return ret;
70462306a36Sopenharmony_ci		ofs += size;
70562306a36Sopenharmony_ci		len -= size;
70662306a36Sopenharmony_ci	}
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	return 0;
70962306a36Sopenharmony_ci}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci#define DO_XXLOCK_LOCK		1
71262306a36Sopenharmony_ci#define DO_XXLOCK_UNLOCK	2
71362306a36Sopenharmony_cistatic int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk)
71462306a36Sopenharmony_ci{
71562306a36Sopenharmony_ci	int ret = 0;
71662306a36Sopenharmony_ci	struct map_info *map = mtd->priv;
71762306a36Sopenharmony_ci	struct lpddr_private *lpddr = map->fldrv_priv;
71862306a36Sopenharmony_ci	int chipnum = adr >> lpddr->chipshift;
71962306a36Sopenharmony_ci	struct flchip *chip = &lpddr->chips[chipnum];
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci	mutex_lock(&chip->mutex);
72262306a36Sopenharmony_ci	ret = get_chip(map, chip, FL_LOCKING);
72362306a36Sopenharmony_ci	if (ret) {
72462306a36Sopenharmony_ci		mutex_unlock(&chip->mutex);
72562306a36Sopenharmony_ci		return ret;
72662306a36Sopenharmony_ci	}
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	if (thunk == DO_XXLOCK_LOCK) {
72962306a36Sopenharmony_ci		send_pfow_command(map, LPDDR_LOCK_BLOCK, adr, adr + len, NULL);
73062306a36Sopenharmony_ci		chip->state = FL_LOCKING;
73162306a36Sopenharmony_ci	} else if (thunk == DO_XXLOCK_UNLOCK) {
73262306a36Sopenharmony_ci		send_pfow_command(map, LPDDR_UNLOCK_BLOCK, adr, adr + len, NULL);
73362306a36Sopenharmony_ci		chip->state = FL_UNLOCKING;
73462306a36Sopenharmony_ci	} else
73562306a36Sopenharmony_ci		BUG();
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	ret = wait_for_ready(map, chip, 1);
73862306a36Sopenharmony_ci	if (ret)	{
73962306a36Sopenharmony_ci		printk(KERN_ERR "%s: block unlock error status %d \n",
74062306a36Sopenharmony_ci				map->name, ret);
74162306a36Sopenharmony_ci		goto out;
74262306a36Sopenharmony_ci	}
74362306a36Sopenharmony_ciout:	put_chip(map, chip);
74462306a36Sopenharmony_ci	mutex_unlock(&chip->mutex);
74562306a36Sopenharmony_ci	return ret;
74662306a36Sopenharmony_ci}
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_cistatic int lpddr_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
74962306a36Sopenharmony_ci{
75062306a36Sopenharmony_ci	return do_xxlock(mtd, ofs, len, DO_XXLOCK_LOCK);
75162306a36Sopenharmony_ci}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_cistatic int lpddr_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
75462306a36Sopenharmony_ci{
75562306a36Sopenharmony_ci	return do_xxlock(mtd, ofs, len, DO_XXLOCK_UNLOCK);
75662306a36Sopenharmony_ci}
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
75962306a36Sopenharmony_ciMODULE_AUTHOR("Alexey Korolev <akorolev@infradead.org>");
76062306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD driver for LPDDR flash chips");
761