/* * This modules maps additional mtd partions of the Motorola Milestone * * Copyright (C) 2010 Janne Grunau * Copyright (C) 2010 Mike Baker * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */#include <linux/module.h>#include <linux/platform_device.h>#include <linux/err.h>#include <linux/mtd/mtd.h>#include <linux/mtd/partitions.h>#include <linux/mtd/nand.h>#include <linux/sched.h>#define mtd_hack_part(pname, pstart, pend) .name = pname, \ .offset = pstart, \ .size = pend - pstart, \struct mtd_partition part[] = { { mtd_hack_part("h_hack", 0x00000000, 0x00620000) },};/** * nand_check_wp - [GENERIC] check if the chip is write protected * @mtd: MTD device structure * Check, if the device is write protected * * The function expects, that the device is already selected */static int nand_check_wp(struct mtd_info *mtd){ struct nand_chip *chip = mtd->priv; /* Check the WP bit */ chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); return (chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;}/** * nand_block_checkbad - [GENERIC] Check if a block is marked bad * @mtd: MTD device structure * @ofs: offset from device start * @getchip: 0, if the chip is already selected * @allowbbt: 1, if its allowed to access the bbt area * * Check, if the block is bad. Either by reading the bad block table or * calling of the scan function. */static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, int allowbbt){ struct nand_chip *chip = mtd->priv; if (!chip->bbt) return chip->block_bad(mtd, ofs, getchip); /* Return info from the table */ return nand_isbad_bbt(mtd, ofs, allowbbt);}/** * nand_get_device - [GENERIC] Get chip for selected access * @chip: the nand chip descriptor * @mtd: MTD device structure * @new_state: the state which is requested * * Get the device and lock it for exclusive access */static intnand_get_device(struct nand_chip *chip, struct mtd_info *mtd, int new_state){ spinlock_t *lock = &chip->controller->lock; wait_queue_head_t *wq = &chip->controller->wq; DECLARE_WAITQUEUE(wait, current); retry: spin_lock(lock); /* Hardware controller shared among independend devices */ /* Hardware controller shared among independend devices */ if (!chip->controller->active) chip->controller->active = chip; if (chip->controller->active == chip && chip->state == FL_READY) { chip->state = new_state; spin_unlock(lock); return 0; } if (new_state == FL_PM_SUSPENDED) { spin_unlock(lock); return (chip->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN; } set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(wq, &wait); spin_unlock(lock); schedule(); remove_wait_queue(wq, &wait); goto retry;}/** * nand_release_device - [GENERIC] release chip * @mtd: MTD device structure * * Deselect, release chip lock and wake up anyone waiting on the device */static void nand_release_device(struct mtd_info *mtd){ struct nand_chip *chip = mtd->priv; /* De-select the NAND device */ chip->select_chip(mtd, -1); /* Release the controller and the chip */ spin_lock(&chip->controller->lock); chip->controller->active = NULL; chip->state = FL_READY; wake_up(&chip->controller->wq); spin_unlock(&chip->controller->lock);}#define BBT_PAGE_MASK 0xffffff3f/** * nand_erase_nand - [Internal] erase block(s) * @mtd: MTD device structure * @instr: erase instruction * * Erase one ore more blocks */int nand_erase_nand_hack(struct mtd_info *mtd, struct erase_info *instr){ int allowbbt = 0; int page, status, pages_per_block, ret, chipnr; struct nand_chip *chip = mtd->priv; loff_t rewrite_bbt[NAND_MAX_CHIPS]={0}; unsigned int bbt_masked_page = 0xffffffff; loff_t len; DEBUG(MTD_DEBUG_LEVEL3, "nand_erase: start = 0x%012llx, len = %llu\n", (unsigned long long)instr->addr, (unsigned long long)instr->len);#if 0 /* if attempt to erase nand flash physical block# 0, * dump stack and panic the phone ... */ if (!(instr->addr)) { dump_stack(); panic("%s: nand flash physical block: 0x%12llx\n", __func__, (unsigned long long)instr->addr); }#endif /* Start address must align on block boundary */ if (instr->addr & ((1 << chip->phys_erase_shift) - 1)) { DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: Unaligned address\n"); return -EINVAL; } /* Length must align on block boundary */ if (instr->len & ((1 << chip->phys_erase_shift) - 1)) { DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " "Length not block aligned\n"); return -EINVAL; } /* Do not allow erase past end of device */ if ((instr->len + instr->addr) > mtd->size) { DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " "Erase past end of device\n"); return -EINVAL; } instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; /* Grab the lock and see if the device is available */ nand_get_device(chip, mtd, FL_ERASING); /* Shift to get first page */ page = (int)(instr->addr >> chip->page_shift); chipnr = (int)(instr->addr >> chip->chip_shift); /* Calculate pages in each block */ pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift); /* Select the NAND device */ chip->select_chip(mtd, chipnr); /* Check, if it is write protected */ if (nand_check_wp(mtd)) { DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " "Device is write protected!!!\n"); instr->state = MTD_ERASE_FAILED; goto erase_exit; } /* * If BBT requires refresh, set the BBT page mask to see if the BBT * should be rewritten. Otherwise the mask is set to 0xffffffff which * can not be matched. This is also done when the bbt is actually * erased to avoid recusrsive updates */ if (chip->options & BBT_AUTO_REFRESH && !allowbbt) bbt_masked_page = chip->bbt_td->pages[chipnr] & BBT_PAGE_MASK; /* Loop through the pages */ len = instr->len; instr->state = MTD_ERASING; while (len) { /* * heck if we have a bad block, we do not erase bad blocks ! */ if (nand_block_checkbad(mtd, ((loff_t) page) << chip->page_shift, 0, allowbbt)) { printk(KERN_WARNING "nand_erase: attempt to erase a " "bad block at page 0x%08x\n", page); instr->state = MTD_ERASE_FAILED; goto erase_exit; } /* * Invalidate the page cache, if we erase the block which * contains the current cached page */ if (page <= chip->pagebuf && chip->pagebuf < (page + pages_per_block)) chip->pagebuf = -1; chip->erase_cmd(mtd, page & chip->pagemask); status = chip->waitfunc(mtd, chip); /* * See if operation failed and additional status checks are * available */ if ((status & NAND_STATUS_FAIL) && (chip->errstat)) status = chip->errstat(mtd, chip, FL_ERASING, status, page); /* See if block erase succeeded */ if (status & NAND_STATUS_FAIL) { DEBUG(MTD_DEBUG_LEVEL0, "nand_erase: " "Failed erase, page 0x%08x\n", page); instr->state = MTD_ERASE_FAILED; instr->fail_addr = ((loff_t)page << chip->page_shift); goto erase_exit; } /* * If BBT requires refresh, set the BBT rewrite flag to the * page being erased */ if (bbt_masked_page != 0xffffffff && (page & BBT_PAGE_MASK) == bbt_masked_page) rewrite_bbt[chipnr] = ((loff_t)page << chip->page_shift); /* Increment page address and decrement length */ len -= (1 << chip->phys_erase_shift); page += pages_per_block; /* Check, if we cross a chip boundary */ if (len && !(page & chip->pagemask)) { chipnr++; chip->select_chip(mtd, -1); chip->select_chip(mtd, chipnr); /* * If BBT requires refresh and BBT-PERCHIP, set the BBT * page mask to see if this BBT should be rewritten */ if (bbt_masked_page != 0xffffffff && (chip->bbt_td->options & NAND_BBT_PERCHIP)) bbt_masked_page = chip->bbt_td->pages[chipnr] & BBT_PAGE_MASK; } } instr->state = MTD_ERASE_DONE; erase_exit: ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; /* Deselect and wake up anyone waiting on the device */ nand_release_device(mtd); /* Do call back function */ if (!ret) mtd_erase_callback(instr); /* * If BBT requires refresh and erase was successful, rewrite any * selected bad block tables */ if (bbt_masked_page == 0xffffffff || ret) return ret; for (chipnr = 0; chipnr < chip->numchips; chipnr++) { if (!rewrite_bbt[chipnr]) continue; /* update the BBT for chip */ DEBUG(MTD_DEBUG_LEVEL0, "nand_erase_nand: nand_update_bbt " "(%d:0x%0llx 0x%0x)\n", chipnr, rewrite_bbt[chipnr], chip->bbt_td->pages[chipnr]); nand_update_bbt(mtd, rewrite_bbt[chipnr]); } /* Return more or less happy */ return ret;}static int create_missing_flash_parts(struct device *dev, void *data){ struct mtd_info *mtd = NULL; printk(KERN_INFO "mtd-hack: device %s\n", dev->init_name); mtd = dev_get_drvdata(dev); if (!mtd) return -1; printk(KERN_INFO "mtd-hack: mtd name %s, type %d, size %llu\n", mtd->name, mtd->type, mtd->size); add_mtd_partitions(mtd, part, sizeof(part)/sizeof(struct mtd_partition)); mtd->erase = nand_erase_nand_hack; return 0;}static int __init mtd_init(void){ struct device_driver *devdrv; int err = 0; // struct device_driver *driver_find(const char *name, struct bus_type *bus); devdrv = driver_find("omap2-nand", &platform_bus_type); printk(KERN_INFO "mtd-hack: found driver %s modname %s\n", devdrv->name, devdrv->mod_name); // int driver_for_each_device(struct device_driver *drv, struct device *start, // void *data, int (*fn)(struct device *, void *)) err = driver_for_each_device(devdrv, NULL, NULL, create_missing_flash_parts); printk(KERN_INFO "mtd hack loaded"); return 0;}module_init(mtd_init);