📄 onenand_base.c
字号:
/* * linux/drivers/mtd/onenand/onenand_base.c * * Copyright (C) 2005-2007 Samsung Electronics * Kyungmin Park <kyungmin.park@samsung.com> * * Credits: * Adrian Hunter <ext-adrian.hunter@nokia.com>: * auto-placement support, read-while load support, various fixes * Copyright (C) Nokia Corporation, 2007 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/sched.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/jiffies.h>#include <linux/mtd/mtd.h>#include <linux/mtd/onenand.h>#include <linux/mtd/partitions.h>#include <asm/io.h>/** * onenand_oob_64 - oob info for large (2KB) page */static struct nand_ecclayout onenand_oob_64 = { .eccbytes = 20, .eccpos = { 8, 9, 10, 11, 12, 24, 25, 26, 27, 28, 40, 41, 42, 43, 44, 56, 57, 58, 59, 60, }, .oobfree = { {2, 3}, {14, 2}, {18, 3}, {30, 2}, {34, 3}, {46, 2}, {50, 3}, {62, 2} }};/** * onenand_oob_32 - oob info for middle (1KB) page */static struct nand_ecclayout onenand_oob_32 = { .eccbytes = 10, .eccpos = { 8, 9, 10, 11, 12, 24, 25, 26, 27, 28, }, .oobfree = { {2, 3}, {14, 2}, {18, 3}, {30, 2} }};static const unsigned char ffchars[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 48 */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 64 */};/** * onenand_readw - [OneNAND Interface] Read OneNAND register * @param addr address to read * * Read OneNAND register */static unsigned short onenand_readw(void __iomem *addr){ return readw(addr);}/** * onenand_writew - [OneNAND Interface] Write OneNAND register with value * @param value value to write * @param addr address to write * * Write OneNAND register with value */static void onenand_writew(unsigned short value, void __iomem *addr){ writew(value, addr);}/** * onenand_block_address - [DEFAULT] Get block address * @param this onenand chip data structure * @param block the block * @return translated block address if DDP, otherwise same * * Setup Start Address 1 Register (F100h) */static int onenand_block_address(struct onenand_chip *this, int block){ /* Device Flash Core select, NAND Flash Block Address */ if (block & this->density_mask) return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); return block;}/** * onenand_bufferram_address - [DEFAULT] Get bufferram address * @param this onenand chip data structure * @param block the block * @return set DBS value if DDP, otherwise 0 * * Setup Start Address 2 Register (F101h) for DDP */static int onenand_bufferram_address(struct onenand_chip *this, int block){ /* Device BufferRAM Select */ if (block & this->density_mask) return ONENAND_DDP_CHIP1; return ONENAND_DDP_CHIP0;}/** * onenand_page_address - [DEFAULT] Get page address * @param page the page address * @param sector the sector address * @return combined page and sector address * * Setup Start Address 8 Register (F107h) */static int onenand_page_address(int page, int sector){ /* Flash Page Address, Flash Sector Address */ int fpa, fsa; fpa = page & ONENAND_FPA_MASK; fsa = sector & ONENAND_FSA_MASK; return ((fpa << ONENAND_FPA_SHIFT) | fsa);}/** * onenand_buffer_address - [DEFAULT] Get buffer address * @param dataram1 DataRAM index * @param sectors the sector address * @param count the number of sectors * @return the start buffer value * * Setup Start Buffer Register (F200h) */static int onenand_buffer_address(int dataram1, int sectors, int count){ int bsa, bsc; /* BufferRAM Sector Address */ bsa = sectors & ONENAND_BSA_MASK; if (dataram1) bsa |= ONENAND_BSA_DATARAM1; /* DataRAM1 */ else bsa |= ONENAND_BSA_DATARAM0; /* DataRAM0 */ /* BufferRAM Sector Count */ bsc = count & ONENAND_BSC_MASK; return ((bsa << ONENAND_BSA_SHIFT) | bsc);}/** * onenand_get_density - [DEFAULT] Get OneNAND density * @param dev_id OneNAND device ID * * Get OneNAND density from device ID */static inline int onenand_get_density(int dev_id){ int density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; return (density & ONENAND_DEVICE_DENSITY_MASK);}/** * onenand_command - [DEFAULT] Send command to OneNAND device * @param mtd MTD device structure * @param cmd the command to be sent * @param addr offset to read from or write to * @param len number of bytes to read or write * * Send command to OneNAND device. This function is used for middle/large page * devices (1KB/2KB Bytes per page) */static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t len){ struct onenand_chip *this = mtd->priv; int value, block, page; /* Address translation */ switch (cmd) { case ONENAND_CMD_UNLOCK: case ONENAND_CMD_LOCK: case ONENAND_CMD_LOCK_TIGHT: case ONENAND_CMD_UNLOCK_ALL: block = -1; page = -1; break; case ONENAND_CMD_ERASE: case ONENAND_CMD_BUFFERRAM: case ONENAND_CMD_OTP_ACCESS: block = (int) (addr >> this->erase_shift); page = -1; break; default: block = (int) (addr >> this->erase_shift); page = (int) (addr >> this->page_shift); if (ONENAND_IS_2PLANE(this)) { /* Make the even block number */ block &= ~1; /* Is it the odd plane? */ if (addr & this->writesize) block++; page >>= 1; } page &= this->page_mask; break; } /* NOTE: The setting order of the registers is very important! */ if (cmd == ONENAND_CMD_BUFFERRAM) { /* Select DataRAM for DDP */ value = onenand_bufferram_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); if (ONENAND_IS_2PLANE(this)) /* It is always BufferRAM0 */ ONENAND_SET_BUFFERRAM0(this); else /* Switch to the next data buffer */ ONENAND_SET_NEXT_BUFFERRAM(this); return 0; } if (block != -1) { /* Write 'DFS, FBA' of Flash */ value = onenand_block_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); /* Select DataRAM for DDP */ value = onenand_bufferram_address(this, block); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); } if (page != -1) { /* Now we use page size operation */ int sectors = 4, count = 4; int dataram; switch (cmd) { case ONENAND_CMD_READ: case ONENAND_CMD_READOOB: dataram = ONENAND_SET_NEXT_BUFFERRAM(this); break; default: if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG) cmd = ONENAND_CMD_2X_PROG; dataram = ONENAND_CURRENT_BUFFERRAM(this); break; } /* Write 'FPA, FSA' of Flash */ value = onenand_page_address(page, sectors); this->write_word(value, this->base + ONENAND_REG_START_ADDRESS8); /* Write 'BSA, BSC' of DataRAM */ value = onenand_buffer_address(dataram, sectors, count); this->write_word(value, this->base + ONENAND_REG_START_BUFFER); } /* Interrupt clear */ this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT); /* Write command */ this->write_word(cmd, this->base + ONENAND_REG_COMMAND); return 0;}/** * onenand_wait - [DEFAULT] wait until the command is done * @param mtd MTD device structure * @param state state to select the max. timeout value * * Wait for command done. This applies to all OneNAND command * Read can take up to 30us, erase up to 2ms and program up to 350us * according to general OneNAND specs */static int onenand_wait(struct mtd_info *mtd, int state){ struct onenand_chip * this = mtd->priv; unsigned long timeout; unsigned int flags = ONENAND_INT_MASTER; unsigned int interrupt = 0; unsigned int ctrl; /* The 20 msec is enough */ timeout = jiffies + msecs_to_jiffies(20); while (time_before(jiffies, timeout)) { interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); if (interrupt & flags) break; if (state != FL_READING) cond_resched(); } /* To get correct interrupt status in timeout case */ interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); /* * In the Spec. it checks the controller status first * However if you get the correct information in case of * power off recovery (POR) test, it should read ECC status first */ if (interrupt & ONENAND_INT_READ) { int ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS); if (ecc) { if (ecc & ONENAND_ECC_2BIT_ALL) { printk(KERN_ERR "onenand_wait: ECC error = 0x%04x\n", ecc); mtd->ecc_stats.failed++; return -EBADMSG; } else if (ecc & ONENAND_ECC_1BIT_ALL) { printk(KERN_INFO "onenand_wait: correctable ECC error = 0x%04x\n", ecc); mtd->ecc_stats.corrected++; } } } else if (state == FL_READING) { printk(KERN_ERR "onenand_wait: read timeout! ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt); return -EIO; } /* If there's controller error, it's a real error */ if (ctrl & ONENAND_CTRL_ERROR) { printk(KERN_ERR "onenand_wait: controller error = 0x%04x\n", ctrl); if (ctrl & ONENAND_CTRL_LOCK) printk(KERN_ERR "onenand_wait: it's locked error.\n"); return -EIO; } return 0;}/* * onenand_interrupt - [DEFAULT] onenand interrupt handler * @param irq onenand interrupt number * @param dev_id interrupt data * * complete the work */static irqreturn_t onenand_interrupt(int irq, void *data){ struct onenand_chip *this = data; /* To handle shared interrupt */ if (!this->complete.done) complete(&this->complete); return IRQ_HANDLED;}/* * onenand_interrupt_wait - [DEFAULT] wait until the command is done * @param mtd MTD device structure * @param state state to select the max. timeout value * * Wait for command done. */static int onenand_interrupt_wait(struct mtd_info *mtd, int state){ struct onenand_chip *this = mtd->priv; wait_for_completion(&this->complete); return onenand_wait(mtd, state);}/* * onenand_try_interrupt_wait - [DEFAULT] try interrupt wait * @param mtd MTD device structure * @param state state to select the max. timeout value * * Try interrupt based wait (It is used one-time) */static int onenand_try_interrupt_wait(struct mtd_info *mtd, int state){ struct onenand_chip *this = mtd->priv; unsigned long remain, timeout; /* We use interrupt wait first */ this->wait = onenand_interrupt_wait; timeout = msecs_to_jiffies(100); remain = wait_for_completion_timeout(&this->complete, timeout); if (!remain) { printk(KERN_INFO "OneNAND: There's no interrupt. " "We use the normal wait\n"); /* Release the irq */ free_irq(this->irq, this); this->wait = onenand_wait; } return onenand_wait(mtd, state);}/* * onenand_setup_wait - [OneNAND Interface] setup onenand wait method * @param mtd MTD device structure * * There's two method to wait onenand work * 1. polling - read interrupt status register * 2. interrupt - use the kernel interrupt method */static void onenand_setup_wait(struct mtd_info *mtd){ struct onenand_chip *this = mtd->priv; int syscfg; init_completion(&this->complete); if (this->irq <= 0) { this->wait = onenand_wait; return; } if (request_irq(this->irq, &onenand_interrupt, IRQF_SHARED, "onenand", this)) { /* If we can't get irq, use the normal wait */ this->wait = onenand_wait; return; } /* Enable interrupt */ syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); syscfg |= ONENAND_SYS_CFG1_IOBE; this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); this->wait = onenand_try_interrupt_wait;}/** * onenand_bufferram_offset - [DEFAULT] BufferRAM offset * @param mtd MTD data structure * @param area BufferRAM area * @return offset given area * * Return BufferRAM offset given area */static inline int onenand_bufferram_offset(struct mtd_info *mtd, int area){ struct onenand_chip *this = mtd->priv; if (ONENAND_CURRENT_BUFFERRAM(this)) { /* Note: the 'this->writesize' is a real page size */ if (area == ONENAND_DATARAM) return this->writesize; if (area == ONENAND_SPARERAM) return mtd->oobsize; } return 0;}/** * onenand_read_bufferram - [OneNAND Interface] Read the bufferram area * @param mtd MTD data structure * @param area BufferRAM area * @param buffer the databuffer to put/get data * @param offset offset to read from or write to * @param count number of bytes to read/write * * Read the BufferRAM area */static int onenand_read_bufferram(struct mtd_info *mtd, int area, unsigned char *buffer, int offset, size_t count){ struct onenand_chip *this = mtd->priv; void __iomem *bufferram; bufferram = this->base + area; bufferram += onenand_bufferram_offset(mtd, area); if (ONENAND_CHECK_BYTE_ACCESS(count)) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -