[PATCH v9 1/3] MTD : add the common code for GPMI-NAND controller driver
From: Huang Shijie <hidden>
Date: 2011-08-22 04:59:11
Hi, thanks for your comments.
quoted
+ +static inline int get_ecc_chunk_size(struct gpmi_nand_data *this) +{ + /* for historical reason */ + return 512;Can't we just #define this? Or will there ever be something else possible here ? I thought this is the only possible behaviour on MXS.
Please keep it here, it maybe changed in the future. It ever had some code for ONFI nand, but i removed it.
quoted
+} + +int common_nfc_set_geometry(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo =&this->bch_geometry; + struct mtd_info *mtd =&this->mil.mtd; + unsigned int metadata_size; + unsigned int status_size; + unsigned int chunk_data_size_in_bits; + unsigned int chunk_ecc_size_in_bits; + unsigned int chunk_total_size_in_bits; + unsigned int block_mark_chunk_number; + unsigned int block_mark_chunk_bit_offset; + unsigned int block_mark_bit_offset; + int gf_len = 13;/* use GP13 by default */ + + /* We only support BCH now. */ + geo->ecc_algorithm = "BCH"; + + /* + * We always choose a metadata size of 10. Don't try to make sense of + * it -- this is really only for historical compatibility. + */Historical compat or you mean "the chip was designed this way, see datasheet section x.y.z"? ;-)
Just for historical compatibility. it's better to keep it as now, there is no need to change it.
quoted
+ geo->metadata_size_in_bytes = 10; + + /* ECC chunks */ + geo->ecc_chunk_size_in_bytes = get_ecc_chunk_size(this); + + /* + * Compute the total number of ECC chunks in a page. This includes the + * slightly larger chunk at the beginning of the page, which contains + * both data and metadata. + */ + geo->ecc_chunk_count = mtd->writesize / geo->ecc_chunk_size_in_bytes; + + /* + * We use the same ECC strength for all chunks, including the first one. + */ + geo->ecc_strength = get_ecc_strength(this); + if (!geo->ecc_strength) { + pr_info("Page size:%d, OOB:%d\n", mtd->writesize, mtd->oobsize); + return -EINVAL; + } + + /* Compute the page size, include page and oob. */ + geo->page_size_in_bytes = mtd->writesize + mtd->oobsize; + geo->payload_size_in_bytes = mtd->writesize; + + /* + * In principle, computing the auxiliary buffer geometry is NFC + * version-specific. However, at this writing, all versions share the + * same model, so this code can also be shared. + * + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + metadata_size = ALIGN(geo->metadata_size_in_bytes, 4); + status_size = ALIGN(geo->ecc_chunk_count, 4); + + geo->auxiliary_size_in_bytes = metadata_size + status_size; + geo->auxiliary_status_offset = metadata_size; + + /* Check if we're going to do block mark swapping. */ + if (!this->swap_block_mark) + return 0; + + /* + * If control arrives here, we're doing block mark swapping, so we need + * to compute the byte and bit offsets of the physical block mark within + * the ECC-based view of the page data. In principle, this isn't a + * difficult computation -- but it's very important and it's easy to get + * it wrong, so we do it carefully. + * + * Note that this calculation is simpler because we use the same ECC + * strength for all chunks, including the zero'th one, which contains + * the metadata. The calculation would be slightly more complicated + * otherwise. + * + * We start by computing the physical bit offset of the block mark. We + * then subtract the number of metadata and ECC bits appearing before + * the mark to arrive at its bit offset within the data alone. + */ + + /* Compute some important facts about chunk geometry. */ + chunk_data_size_in_bits = geo->ecc_chunk_size_in_bytes * 8; + chunk_ecc_size_in_bits = geo->ecc_strength * gf_len; + chunk_total_size_in_bits = chunk_data_size_in_bits + + chunk_ecc_size_in_bits; + + /* Compute the bit offset of the block mark within the physical page. */ + block_mark_bit_offset = mtd->writesize * 8; + + /* Subtract the metadata bits. */ + block_mark_bit_offset -= geo->metadata_size_in_bytes * 8; + + /* + * Compute the chunk number (starting at zero) in which the block mark + * appears. + */ + block_mark_chunk_number = + block_mark_bit_offset / chunk_total_size_in_bits; + + /* + * Compute the bit offset of the block mark within its chunk, and + * validate it. + */ + block_mark_chunk_bit_offset = + block_mark_bit_offset - + (block_mark_chunk_number * chunk_total_size_in_bits); + + if (block_mark_chunk_bit_offset> chunk_data_size_in_bits) { + /* + * If control arrives here, the block mark actually appears in + * the ECC bits of this chunk. This wont' work. + */ + pr_info("Unsupported page geometry : %u:%u\n", + mtd->writesize, mtd->oobsize); + return -EINVAL; + } + + /* + * Now that we know the chunk number in which the block mark appears, + * we can subtract all the ECC bits that appear before it. + */ + block_mark_bit_offset -= + block_mark_chunk_number * chunk_ecc_size_in_bits; + + /* + * We now know the absolute bit offset of the block mark within the + * ECC-based data. We can now compute the byte offset and the bit + * offset within the byte. + */ + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + + return 0; +} + +struct dma_chan *get_dma_chan(struct gpmi_nand_data *this) +{ + int chip = this->mil.current_chip; + + BUG_ON(chip< 0); + return this->dma_chans[chip]; +} + +/* Can we use the upper's buffer directly for DMA? */ +void prepare_data_dma(struct gpmi_nand_data *this, enum dma_data_direction dr) +{ + struct mil *mil =&this->mil; + struct scatterlist *sgl =&mil->data_sgl;I still see the "MIL" present -- can't we just merge the library and this ?
the `mil` is just a data structure to contain the fields now. Maybe I should change the name of it.
quoted
+ int ret; + + mil->direct_dma_map_ok = true; + + /* first try to map the upper buffer directly */ + sg_init_one(sgl, mil->upper_buf, mil->upper_len); + ret = dma_map_sg(this->dev, sgl, 1, dr); + if (ret == 0) { + /* We have to use our own DMA buffer. */ + sg_init_one(sgl, mil->data_buffer_dma, PAGE_SIZE); + + if (dr == DMA_TO_DEVICE) + memcpy(mil->data_buffer_dma, mil->upper_buf, + mil->upper_len); + + ret = dma_map_sg(this->dev, sgl, 1, dr); + BUG_ON(ret == 0); + + mil->direct_dma_map_ok = false; + } +} + +/* This will be called after the DMA operation is finished. */ +static void dma_irq_callback(void *param) +{ + struct gpmi_nand_data *this = param; + struct mil *mil =&this->mil; + struct completion *dma_c =&this->dma_done; + + complete(dma_c); + + switch (this->dma_type) { + case DMA_FOR_COMMAND: + dma_unmap_sg(this->dev,&mil->cmd_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_DATA: + dma_unmap_sg(this->dev,&mil->data_sgl, 1, DMA_FROM_DEVICE); + if (mil->direct_dma_map_ok == false) + memcpy(mil->upper_buf, mil->data_buffer_dma, + mil->upper_len); + break; + + case DMA_FOR_WRITE_DATA: + dma_unmap_sg(this->dev,&mil->data_sgl, 1, DMA_TO_DEVICE); + break; + + case DMA_FOR_READ_ECC_PAGE: + case DMA_FOR_WRITE_ECC_PAGE: + /* We have to wait the BCH interrupt to finish. */ + break; + + default: + BUG(); + } +} + +static void show_bch_geometry(struct bch_geometry *geo) +{ + pr_info("---------------------------------------\n"); + pr_info(" BCH Geometry\n"); + pr_info("---------------------------------------\n"); + pr_info("ECC Algorithm : %s\n", geo->ecc_algorithm); + pr_info("ECC Strength : %u\n", geo->ecc_strength); + pr_info("Page Size in Bytes : %u\n", geo->page_size_in_bytes); + pr_info("Metadata Size in Bytes : %u\n", geo->metadata_size_in_bytes); + pr_info("ECC Chunk Size in Bytes: %u\n", geo->ecc_chunk_size_in_bytes); + pr_info("ECC Chunk Count : %u\n", geo->ecc_chunk_count); + pr_info("Payload Size in Bytes : %u\n", geo->payload_size_in_bytes); + pr_info("Auxiliary Size in Bytes: %u\n", geo->auxiliary_size_in_bytes); + pr_info("Auxiliary Status Offset: %u\n", geo->auxiliary_status_offset); + pr_info("Block Mark Byte Offset : %u\n", geo->block_mark_byte_offset); + pr_info("Block Mark Bit Offset : %u\n", geo->block_mark_bit_offset); +}We don't need this.
I just use it for debug. Why do not need it? :) I think it's useful to debug.
quoted
+ +int start_dma_without_bch_irq(struct gpmi_nand_data *this, + struct dma_async_tx_descriptor *desc) +{ + struct completion *dma_c =&this->dma_done; + int err; + + init_completion(dma_c); + + desc->callback = dma_irq_callback; + desc->callback_param = this; + dmaengine_submit(desc); + + /* Wait for the interrupt from the DMA block. */ + err = wait_for_completion_timeout(dma_c, msecs_to_jiffies(1000)); + err = (!err) ? -ETIMEDOUT : 0; + if (err) { + pr_info("DMA timeout, last DMA :%d\n", this->last_dma_type); + if (gpmi_debug& GPMI_DEBUG_CRAZY) { + struct bch_geometry *geo =&this->bch_geometry; + + gpmi_show_regs(this); + show_bch_geometry(geo); + panic("-----------DMA FAILED------------------");No, dev_err() or something. \
ok, no problem.
Also, I don't like you using pr_ stuff, I think you can use dev_ stuff, can't you?quoted
+ } + } + return err; +} + +/* + * This function is used in BCH reading or BCH writing pages. + * It will wait for the BCH interrupt as long as ONE second. + * Actually, we must wait for two interrupts : + * [1] firstly the DMA interrupt and + * [2] secondly the BCH interrupt. + * + * @this: Per-device data structure. + * @desc: DMA channelDoes this conform to kerneldoc ?
it's redundant, i will remove the description for the parameters. But keep the description for the function.
quoted
+ */ +int start_dma_with_bch_irq(struct gpmi_nand_data *this, + struct dma_async_tx_descriptor *desc) +{ + int err; + + /* Prepare to receive an interrupt from the BCH block. */ + init_completion(&this->bch_done); + + /* start the DMA */ + start_dma_without_bch_irq(this, desc); + + /* Wait for the interrupt from the BCH block. */ + err = wait_for_completion_timeout(&this->bch_done, + msecs_to_jiffies(1000)); + err = (!err) ? -ETIMEDOUT : 0; + if (err) { + pr_info("BCH timeout!!!\n");One ! is enough!!!
ok.
quoted
+ if (gpmi_debug& GPMI_DEBUG_CRAZY) {GPMI_DEBUG_CRAZY should probably be GPMI_DEBUG_VERBOSE ?
ok, thanks
quoted
+ struct bch_geometry *geo =&this->bch_geometry; + + gpmi_show_regs(this); + show_bch_geometry(geo); + panic("-----------BCH FAILED------------------");dev_err()quoted
+ } + } + return err; +} + +static int __devinit acquire_register_block(struct gpmi_nand_data *this, + const char *resource_name, void **reg_block_base) +{ + struct platform_device *pdev = this->pdev; + struct resource *r; + void *p; + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, resource_name); + if (!r) { + pr_info("Can't get resource for %s\n", resource_name); + return -ENXIO; + } + + /* remap the register block */ + p = ioremap(r->start, resource_size(r)); + if (!p) { + pr_info("Can't remap %s\n", resource_name); + return -ENOMEM; + } + + *reg_block_base = p; + return 0; +} + +static void release_register_block(struct gpmi_nand_data *this, + void *reg_block_base) +{ + iounmap(reg_block_base); +} + +static int __devinit acquire_interrupt(struct gpmi_nand_data *this, + const char *resource_name, + irq_handler_t interrupt_handler, int *lno, int *hno) +{ + struct platform_device *pdev = this->pdev; + struct resource *r; + int err; + + r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, resource_name); + if (!r) { + pr_info("Can't get resource for %s\n", resource_name); + return -ENXIO; + } + + BUG_ON(r->start != r->end); + err = request_irq(r->start, interrupt_handler, 0, resource_name, this); + if (err) { + pr_info("Can't own %s\n", resource_name); + return err; + } + + *lno = r->start; + *hno = r->end; + return 0; +} + + +static int __devinit acquire_resources(struct gpmi_nand_data *this) +{ + struct resources *r =&this->resources; + int error; + + /* Attempt to acquire the GPMI register block. */ + error = acquire_register_block(this, + GPMI_NAND_GPMI_REGS_ADDR_RES_NAME, + &r->gpmi_regs);You're already passing "this", why pass r->gpmi_regs? Please fix globally.
ok, thanks
quoted
+static int gpmi_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nand_data *this = nand->priv; + struct mil *mil =&this->mil; + + return gpmi_is_ready(this, mil->current_chip); +} + +static void gpmi_select_chip(struct mtd_info *mtd, int chip) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nand_data *this = nand->priv; + struct mil *mil =&this->mil; + + if ((mil->current_chip< 0)&& (chip>= 0)) + gpmi_begin(this); + else if ((mil->current_chip>= 0)&& (chip< 0)) + gpmi_end(this); + else + ;Do you need this else branch at all?
It needs a warning here.
quoted
+ + mil->current_chip = chip; +} + +static void gpmi_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nand_data *this = nand->priv; + struct mil *mil =&this->mil; + + logio(GPMI_DEBUG_READ); + /* save the info in mil{} for future */ + mil->upper_buf = buf; + mil->upper_len = len; + + gpmi_read_data(this); +} + +static void gpmi_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nand_data *this = nand->priv; + struct mil *mil =&this->mil; + + logio(GPMI_DEBUG_WRITE); + /* save the info in mil{} for future */ + mil->upper_buf = (uint8_t *)buf; + mil->upper_len = len; + + gpmi_send_data(this); +} + +static uint8_t gpmi_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nand_data *this = nand->priv; + struct mil *mil =&this->mil; + uint8_t *buf = mil->data_buffer_dma; + + gpmi_read_buf(mtd, buf, 1); + return buf[0]; +} + +static int gpmi_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *nand = mtd->priv; + struct gpmi_nand_data *this = nand->priv; + int block, ret = 0; + + /* Get block number */ + block = (int)(ofs>> nand->bbt_erase_shift); + if (nand->bbt) + nand->bbt[block>> 2] |= 0x01<< ((block& 0x03)<< 1); + + /* Do we have a flash based bad block table ? */ + if (nand->options& NAND_USE_FLASH_BBT) + ret = nand_update_bbt(mtd, ofs);if (stuff) return nand_update_bbt();
I can not return it here, I need to update the ecc_stats too.
stuff from else branch . . . Besides, please don't declare variables in the middle of code.
why?
it's no harm to declare the variables in the {}.
quoted
+ else { + struct mil *mil =&this->mil; + uint8_t *block_mark; + int column, page, status, chipnr; + + chipnr = (int)(ofs>> nand->chip_shift); + nand->select_chip(mtd, chipnr); + + column = this->swap_block_mark ? mtd->writesize : 0; + + /* Write the block mark. */ + block_mark = mil->data_buffer_dma; + block_mark[0] = 0; /* bad block marker */ + + /* Shift to get page */ + page = (int)(ofs>> nand->page_shift); + + nand->cmdfunc(mtd, NAND_CMD_SEQIN, column, page); + nand->write_buf(mtd, block_mark, 1); + nand->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); + + status = nand->waitfunc(mtd, nand); + if (status& NAND_STATUS_FAIL) + ret = -EIO; + + nand->select_chip(mtd, -1); + } + if (!ret) + mtd->ecc_stats.badblocks++; + + return ret; +} + +static int __devinit nand_boot_set_geometry(struct gpmi_nand_data *this) +{ + struct boot_rom_geometry *geometry =&this->rom_geometry; + + /* + * Set the boot block stride size. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->stride_size_in_pages = 64; + + /* + * Set the search area stride exponent. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->search_area_stride_exponent = 2; + + if (gpmi_debug& GPMI_DEBUG_INIT) + pr_info("stride size in page : %d, search areas : %d\n", + geometry->stride_size_in_pages, + geometry->search_area_stride_exponent); + return 0; +} + +static const char *fingerprint = "STMP"; +static int __devinit mx23_check_transcription_stamp(struct gpmi_nand_data *this) +{ + struct boot_rom_geometry *rom_geo =&this->rom_geometry; + struct mil *mil =&this->mil; + struct mtd_info *mtd =&mil->mtd; + struct nand_chip *nand =&mil->nand; + unsigned int search_area_size_in_strides; + unsigned int stride; + unsigned int page; + loff_t byte; + uint8_t *buffer = nand->buffers->databuf; + int saved_chip_number; + int found_an_ncb_fingerprint = false; + + /* Compute the number of strides in a search area. */ + search_area_size_in_strides = 1<< rom_geo->search_area_stride_exponent; + + /* Select chip 0. */ + saved_chip_number = mil->current_chip; + nand->select_chip(mtd, 0); + + /* + * Loop through the first search area, looking for the NCB fingerprint. + */ + pr_info("Scanning for an NCB fingerprint...\n"); + + for (stride = 0; stride< search_area_size_in_strides; stride++) { + /* Compute the page and byte addresses. */ + page = stride * rom_geo->stride_size_in_pages; + byte = page * mtd->writesize; + + pr_info(" Looking for a fingerprint in page 0x%x\n", page);pr_info? Why, who cares, I'd prefer dev_dbg()?
thanks
quoted
+ + /* + * Read the NCB fingerprint. The fingerprint is four bytes long + * and starts in the 12th byte of the page. + */ + nand->cmdfunc(mtd, NAND_CMD_READ0, 12, page); + nand->read_buf(mtd, buffer, strlen(fingerprint)); + + /* Look for the fingerprint. */ + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { + found_an_ncb_fingerprint = true; + break; + } + + } + + /* Deselect chip 0. */ + nand->select_chip(mtd, saved_chip_number); + + if (found_an_ncb_fingerprint) + pr_info(" Found a fingerprint\n"); + else + pr_info(" No fingerprint found\n"); + return found_an_ncb_fingerprint; +} + +/* Writes a transcription stamp. */ +static int __devinit mx23_write_transcription_stamp(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + struct boot_rom_geometry *rom_geo =&this->rom_geometry; + struct mil *mil =&this->mil; + struct mtd_info *mtd =&mil->mtd; + struct nand_chip *nand =&mil->nand; + unsigned int block_size_in_pages; + unsigned int search_area_size_in_strides; + unsigned int search_area_size_in_pages; + unsigned int search_area_size_in_blocks; + unsigned int block; + unsigned int stride; + unsigned int page; + loff_t byte; + uint8_t *buffer = nand->buffers->databuf; + int saved_chip_number; + int status; + + /* Compute the search area geometry. */ + block_size_in_pages = mtd->erasesize / mtd->writesize; + search_area_size_in_strides = 1<< rom_geo->search_area_stride_exponent; + search_area_size_in_pages = search_area_size_in_strides * + rom_geo->stride_size_in_pages; + search_area_size_in_blocks = + (search_area_size_in_pages + (block_size_in_pages - 1)) / + block_size_in_pages; + + pr_info("-------------------------------------------\n"); + pr_info("Search Area Geometry\n"); + pr_info("-------------------------------------------\n"); + pr_info("Search Area in Blocks : %u\n", search_area_size_in_blocks); + pr_info("Search Area in Strides: %u\n", search_area_size_in_strides); + pr_info("Search Area in Pages : %u\n", search_area_size_in_pages);Maybe if you debug it, yes ... but I certainly don't want such ascii-art in my log during normal operation.
ok.
quoted
addr_t auxiliary); + +/* ONFI or TOGGLE nand */ +bool is_ddr_nand(struct gpmi_nand_data *); + +/* for log */ +extern int gpmi_debug;Why this extern ?
this header can be included by gpmi-nand.c and gpmi-lib.c.
quoted
+#define GPMI_DEBUG_INIT 0x0001 +#define GPMI_DEBUG_READ 0x0002 +#define GPMI_DEBUG_WRITE 0x0004 +#define GPMI_DEBUG_ECC_READ 0x0008 +#define GPMI_DEBUG_ECC_WRITE 0x0010 +#define GPMI_DEBUG_CRAZY 0x0020 + +#ifdef pr_fmt +#undef pr_fmt +#endif + +#define pr_fmt(fmt) "[ %s : %.3d ] " fmt, __func__, __LINE__ + +#define logio(level) \ + do { \ + if (gpmi_debug& level) \ + pr_info("\n"); \ + } while (0)Do you really need this ?
not really. thanks Huang Shijie