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