mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	https://www.kernel.org/pub/linux/kernel/v4.x/ChangeLog-4.1.11 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de> SVN-Revision: 47252
		
			
				
	
	
		
			888 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			888 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From ef4bc8ab68979e5c1c30f061c5af1a7d6ec8eb52 Mon Sep 17 00:00:00 2001
 | |
| From: Boris Brezillon <boris.brezillon@free-electrons.com>
 | |
| Date: Tue, 21 Oct 2014 14:40:42 +0200
 | |
| Subject: [PATCH] mtd: nand: sunxi: Add HW randomizer support
 | |
| 
 | |
| Add support for the HW randomizer available on the sunxi nand controller.
 | |
| 
 | |
| Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
 | |
| Signed-off-by: Hans de Goede <hdegoede@redhat.com>
 | |
| ---
 | |
|  drivers/mtd/nand/sunxi_nand.c | 603 ++++++++++++++++++++++++++++++++++++++++--
 | |
|  1 file changed, 585 insertions(+), 18 deletions(-)
 | |
| 
 | |
| --- a/drivers/mtd/nand/sunxi_nand.c
 | |
| +++ b/drivers/mtd/nand/sunxi_nand.c
 | |
| @@ -210,10 +210,12 @@ struct sunxi_nand_hw_ecc {
 | |
|   *
 | |
|   * @part: base paritition structure
 | |
|   * @ecc: per-partition ECC info
 | |
| + * @rnd: per-partition randomizer info
 | |
|   */
 | |
|  struct sunxi_nand_part {
 | |
|  	struct nand_part part;
 | |
|  	struct nand_ecc_ctrl ecc;
 | |
| +	struct nand_rnd_ctrl rnd;
 | |
|  };
 | |
|  
 | |
|  static inline struct sunxi_nand_part *
 | |
| @@ -223,6 +225,29 @@ to_sunxi_nand_part(struct nand_part *par
 | |
|  }
 | |
|  
 | |
|  /*
 | |
| + * sunxi NAND randomizer structure: stores NAND randomizer information
 | |
| + *
 | |
| + * @page: current page
 | |
| + * @column: current column
 | |
| + * @nseeds: seed table size
 | |
| + * @seeds: seed table
 | |
| + * @subseeds: pre computed sub seeds
 | |
| + * @step: step function
 | |
| + * @left: number of remaining bytes in the page
 | |
| + * @state: current randomizer state
 | |
| + */
 | |
| +struct sunxi_nand_hw_rnd {
 | |
| +	int page;
 | |
| +	int column;
 | |
| +	int nseeds;
 | |
| +	u16 *seeds;
 | |
| +	u16 *subseeds;
 | |
| +	u16 (*step)(struct mtd_info *mtd, u16 state, int column, int *left);
 | |
| +	int left;
 | |
| +	u16 state;
 | |
| +};
 | |
| +
 | |
| +/*
 | |
|   * NAND chip structure: stores NAND chip device related information
 | |
|   *
 | |
|   * @node:		used to store NAND chips into a list
 | |
| @@ -237,6 +262,7 @@ struct sunxi_nand_chip {
 | |
|  	struct list_head node;
 | |
|  	struct nand_chip nand;
 | |
|  	struct mtd_info mtd;
 | |
| +	void *buffer;
 | |
|  	unsigned long clk_rate;
 | |
|  	int selected;
 | |
|  	int nsels;
 | |
| @@ -493,6 +519,185 @@ static void sunxi_nfc_write_buf(struct m
 | |
|  	}
 | |
|  }
 | |
|  
 | |
| +static u16 sunxi_nfc_hwrnd_step(struct sunxi_nand_hw_rnd *rnd, u16 state, int count)
 | |
| +{
 | |
| +	state &= 0x7fff;
 | |
| +	count *= 8;
 | |
| +	while (count--)
 | |
| +		state = ((state >> 1) |
 | |
| +			 ((((state >> 0) ^ (state >> 1)) & 1) << 14)) & 0x7fff;
 | |
| +
 | |
| +	return state;
 | |
| +}
 | |
| +
 | |
| +static u16 sunxi_nfc_hwrnd_single_step(u16 state, int count)
 | |
| +{
 | |
| +	state &= 0x7fff;
 | |
| +	while (count--)
 | |
| +		state = ((state >> 1) |
 | |
| +			 ((((state >> 0) ^ (state >> 1)) & 1) << 14)) & 0x7fff;
 | |
| +
 | |
| +	return state;
 | |
| +}
 | |
| +
 | |
| +static int sunxi_nfc_hwrnd_config(struct mtd_info *mtd, int page, int column,
 | |
| +				  enum nand_rnd_action action)
 | |
| +{
 | |
| +	struct nand_chip *nand = mtd->priv;
 | |
| +	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
 | |
| +	struct sunxi_nand_hw_rnd *rnd = nand->cur_rnd->priv;
 | |
| +	u16 state;
 | |
| +
 | |
| +	if (page < 0 && column < 0) {
 | |
| +		rnd->page = -1;
 | |
| +		rnd->column = -1;
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	if (column < 0)
 | |
| +		column = 0;
 | |
| +	if (page < 0)
 | |
| +		page = rnd->page;
 | |
| +
 | |
| +	if (page < 0)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	if (page != rnd->page && action == NAND_RND_READ) {
 | |
| +		int status;
 | |
| +
 | |
| +		status = nand_page_get_status(mtd, page);
 | |
| +		if (status == NAND_PAGE_STATUS_UNKNOWN) {
 | |
| +			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
 | |
| +			sunxi_nfc_read_buf(mtd, sunxi_nand->buffer,
 | |
| +					   mtd->writesize + mtd->oobsize);
 | |
| +
 | |
| +			if (nand_page_is_empty(mtd, sunxi_nand->buffer,
 | |
| +					       sunxi_nand->buffer +
 | |
| +					       mtd->writesize))
 | |
| +				status = NAND_PAGE_EMPTY;
 | |
| +			else
 | |
| +				status = NAND_PAGE_FILLED;
 | |
| +
 | |
| +			nand_page_set_status(mtd, page, status);
 | |
| +			nand->cmdfunc(mtd, NAND_CMD_RNDOUT, column, -1);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	state = rnd->seeds[page % rnd->nseeds];
 | |
| +	rnd->page = page;
 | |
| +	rnd->column = column;
 | |
| +
 | |
| +	if (rnd->step) {
 | |
| +		rnd->state = rnd->step(mtd, state, column, &rnd->left);
 | |
| +	} else {
 | |
| +		rnd->state = sunxi_nfc_hwrnd_step(rnd, state, column % 4096);
 | |
| +		rnd->left = mtd->oobsize + mtd->writesize - column;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void sunxi_nfc_hwrnd_write_buf(struct mtd_info *mtd, const uint8_t *buf,
 | |
| +				      int len)
 | |
| +{
 | |
| +	struct nand_chip *nand = mtd->priv;
 | |
| +	struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
 | |
| +	struct sunxi_nand_hw_rnd *rnd = nand->cur_rnd->priv;
 | |
| +	u32 tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
| +	int cnt;
 | |
| +	int offs = 0;
 | |
| +	int rndactiv;
 | |
| +
 | |
| +	tmp &= ~(NFC_RANDOM_DIRECTION | NFC_RANDOM_SEED | NFC_RANDOM_EN);
 | |
| +	writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
| +	if (rnd->page < 0) {
 | |
| +		sunxi_nfc_write_buf(mtd, buf, len);
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	while (len > offs) {
 | |
| +		cnt = len - offs;
 | |
| +		if (cnt > 1024)
 | |
| +			cnt = 1024;
 | |
| +
 | |
| +		rndactiv = nand_rnd_is_activ(mtd, rnd->page, rnd->column,
 | |
| +					     &cnt);
 | |
| +		if (rndactiv > 0) {
 | |
| +			writel(tmp | NFC_RANDOM_EN | (rnd->state << 16),
 | |
| +			       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +			if (rnd->left < cnt)
 | |
| +				cnt = rnd->left;
 | |
| +		}
 | |
| +
 | |
| +		sunxi_nfc_write_buf(mtd, buf + offs, cnt);
 | |
| +
 | |
| +		if (rndactiv > 0)
 | |
| +			writel(tmp & ~NFC_RANDOM_EN,
 | |
| +			       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
| +		offs += cnt;
 | |
| +		if (len <= offs)
 | |
| +			break;
 | |
| +
 | |
| +		sunxi_nfc_hwrnd_config(mtd, -1, rnd->column + cnt, NAND_RND_WRITE);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void sunxi_nfc_hwrnd_read_buf(struct mtd_info *mtd, uint8_t *buf,
 | |
| +				     int len)
 | |
| +{
 | |
| +	struct nand_chip *nand = mtd->priv;
 | |
| +	struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
 | |
| +	struct sunxi_nand_hw_rnd *rnd = nand->cur_rnd->priv;
 | |
| +	u32 tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
| +	int cnt;
 | |
| +	int offs = 0;
 | |
| +	int rndactiv;
 | |
| +
 | |
| +	tmp &= ~(NFC_RANDOM_DIRECTION | NFC_RANDOM_SEED | NFC_RANDOM_EN);
 | |
| +	writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
| +	if (rnd->page < 0) {
 | |
| +		sunxi_nfc_read_buf(mtd, buf, len);
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	while (len > offs) {
 | |
| +		cnt = len - offs;
 | |
| +		if (cnt > 1024)
 | |
| +			cnt = 1024;
 | |
| +
 | |
| +		if (nand_page_get_status(mtd, rnd->page) != NAND_PAGE_EMPTY &&
 | |
| +		    nand_rnd_is_activ(mtd, rnd->page, rnd->column, &cnt) > 0)
 | |
| +			rndactiv = 1;
 | |
| +		else
 | |
| +			rndactiv = 0;
 | |
| +
 | |
| +		if (rndactiv > 0) {
 | |
| +			writel(tmp | NFC_RANDOM_EN | (rnd->state << 16),
 | |
| +			       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +			if (rnd->left < cnt)
 | |
| +				cnt = rnd->left;
 | |
| +		}
 | |
| +
 | |
| +		if (buf)
 | |
| +			sunxi_nfc_read_buf(mtd, buf + offs, cnt);
 | |
| +		else
 | |
| +			sunxi_nfc_read_buf(mtd, NULL, cnt);
 | |
| +
 | |
| +		if (rndactiv > 0)
 | |
| +			writel(tmp & ~NFC_RANDOM_EN,
 | |
| +			       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
| +		offs += cnt;
 | |
| +		if (len <= offs)
 | |
| +			break;
 | |
| +
 | |
| +		sunxi_nfc_hwrnd_config(mtd, -1, rnd->column + cnt, NAND_RND_READ);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
|  static uint8_t sunxi_nfc_read_byte(struct mtd_info *mtd)
 | |
|  {
 | |
|  	uint8_t ret;
 | |
| @@ -542,16 +747,43 @@ static int sunxi_nfc_hw_ecc_read_page(st
 | |
|  				      int oob_required, int page)
 | |
|  {
 | |
|  	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
 | |
| +	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
 | |
|  	struct nand_ecc_ctrl *ecc = chip->cur_ecc;
 | |
|  	struct nand_ecclayout *layout = ecc->layout;
 | |
|  	struct sunxi_nand_hw_ecc *data = ecc->priv;
 | |
|  	unsigned int max_bitflips = 0;
 | |
| +	int status;
 | |
|  	int offset;
 | |
|  	int ret;
 | |
|  	u32 tmp;
 | |
|  	int i;
 | |
|  	int cnt;
 | |
|  
 | |
| +	status = nand_page_get_status(mtd, page);
 | |
| +	if (status == NAND_PAGE_STATUS_UNKNOWN) {
 | |
| +		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
 | |
| +		sunxi_nfc_read_buf(mtd, sunxi_nand->buffer,
 | |
| +				   mtd->writesize + mtd->oobsize);
 | |
| +
 | |
| +		if (nand_page_is_empty(mtd, sunxi_nand->buffer,
 | |
| +				       sunxi_nand->buffer +
 | |
| +				       mtd->writesize)) {
 | |
| +			status = NAND_PAGE_EMPTY;
 | |
| +		} else {
 | |
| +			status = NAND_PAGE_FILLED;
 | |
| +			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
 | |
| +		}
 | |
| +
 | |
| +		nand_page_set_status(mtd, page, status);
 | |
| +	}
 | |
| +
 | |
| +	if (status == NAND_PAGE_EMPTY) {
 | |
| +		memset(buf, 0xff, mtd->writesize);
 | |
| +		if (oob_required)
 | |
| +			memset(chip->oob_poi, 0xff, mtd->oobsize);
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
|  	tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
|  	tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE);
 | |
|  	tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT) |
 | |
| @@ -560,12 +792,15 @@ static int sunxi_nfc_hw_ecc_read_page(st
 | |
|  	writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
|  
 | |
|  	for (i = 0; i < ecc->steps; i++) {
 | |
| +		bool rndactiv = false;
 | |
| +
 | |
|  		if (i)
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, i * ecc->size, -1);
 | |
|  
 | |
|  		offset = mtd->writesize + layout->eccpos[i * ecc->bytes] - 4;
 | |
|  
 | |
| -		chip->read_buf(mtd, NULL, ecc->size);
 | |
| +		nand_rnd_config(mtd, page, i * ecc->size, NAND_RND_READ);
 | |
| +		nand_rnd_read_buf(mtd, NULL, ecc->size);
 | |
|  
 | |
|  		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
 | |
|  
 | |
| @@ -573,6 +808,25 @@ static int sunxi_nfc_hw_ecc_read_page(st
 | |
|  		if (ret)
 | |
|  			return ret;
 | |
|  
 | |
| +		if (i) {
 | |
| +			cnt = ecc->bytes + 4;
 | |
| +			if (nand_rnd_is_activ(mtd, page, offset, &cnt) > 0 &&
 | |
| +			    cnt == ecc->bytes + 4)
 | |
| +				rndactiv = true;
 | |
| +		} else {
 | |
| +			cnt = ecc->bytes + 2;
 | |
| +			if (nand_rnd_is_activ(mtd, page, offset + 2, &cnt) > 0 &&
 | |
| +			    cnt == ecc->bytes + 2)
 | |
| +				rndactiv = true;
 | |
| +		}
 | |
| +
 | |
| +		if (rndactiv) {
 | |
| +			tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
| +			tmp &= ~(NFC_RANDOM_DIRECTION | NFC_ECC_EXCEPTION);
 | |
| +			tmp |= NFC_RANDOM_EN;
 | |
| +			writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
| +		}
 | |
| +
 | |
|  		tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30);
 | |
|  		writel(tmp, nfc->regs + NFC_REG_CMD);
 | |
|  
 | |
| @@ -583,6 +837,9 @@ static int sunxi_nfc_hw_ecc_read_page(st
 | |
|  		memcpy_fromio(buf + (i * ecc->size),
 | |
|  			      nfc->regs + NFC_RAM0_BASE, ecc->size);
 | |
|  
 | |
| +		writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN,
 | |
| +		       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
|  		if (readl(nfc->regs + NFC_REG_ECC_ST) & 0x1) {
 | |
|  			mtd->ecc_stats.failed++;
 | |
|  		} else {
 | |
| @@ -598,9 +855,10 @@ static int sunxi_nfc_hw_ecc_read_page(st
 | |
|  			if (ret)
 | |
|  				return ret;
 | |
|  
 | |
| +			nand_rnd_config(mtd, -1, offset, NAND_RND_READ);
 | |
|  			offset -= mtd->writesize;
 | |
| -			chip->read_buf(mtd, chip->oob_poi + offset,
 | |
| -				      ecc->bytes + 4);
 | |
| +			nand_rnd_read_buf(mtd, chip->oob_poi + offset,
 | |
| +					  ecc->bytes + 4);
 | |
|  		}
 | |
|  	}
 | |
|  
 | |
| @@ -610,11 +868,14 @@ static int sunxi_nfc_hw_ecc_read_page(st
 | |
|  			offset = mtd->writesize +
 | |
|  				 ecc->layout->oobfree[ecc->steps].offset;
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
 | |
| +			nand_rnd_config(mtd, -1, offset, NAND_RND_READ);
 | |
|  			offset -= mtd->writesize;
 | |
| -			chip->read_buf(mtd, chip->oob_poi + offset, cnt);
 | |
| +			nand_rnd_read_buf(mtd, chip->oob_poi + offset, cnt);
 | |
|  		}
 | |
|  	}
 | |
|  
 | |
| +	nand_rnd_config(mtd, -1, -1, NAND_RND_READ);
 | |
| +
 | |
|  	tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
|  	tmp &= ~NFC_ECC_EN;
 | |
|  
 | |
| @@ -631,6 +892,7 @@ static int sunxi_nfc_hw_ecc_write_page(s
 | |
|  	struct nand_ecc_ctrl *ecc = chip->cur_ecc;
 | |
|  	struct nand_ecclayout *layout = ecc->layout;
 | |
|  	struct sunxi_nand_hw_ecc *data = ecc->priv;
 | |
| +	struct sunxi_nand_hw_rnd *rnd = chip->cur_rnd->priv;
 | |
|  	int offset;
 | |
|  	int ret;
 | |
|  	u32 tmp;
 | |
| @@ -645,17 +907,57 @@ static int sunxi_nfc_hw_ecc_write_page(s
 | |
|  	writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
|  
 | |
|  	for (i = 0; i < ecc->steps; i++) {
 | |
| +		bool rndactiv = false;
 | |
| +		u8 oob_buf[4];
 | |
| +
 | |
|  		if (i)
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDIN, i * ecc->size, -1);
 | |
|  
 | |
| -		chip->write_buf(mtd, buf + (i * ecc->size), ecc->size);
 | |
| +		nand_rnd_config(mtd, -1, i * ecc->size, NAND_RND_WRITE);
 | |
| +		nand_rnd_write_buf(mtd, buf + (i * ecc->size), ecc->size);
 | |
|  
 | |
|  		offset = layout->eccpos[i * ecc->bytes] - 4 + mtd->writesize;
 | |
|  
 | |
|  		/* Fill OOB data in */
 | |
| -		writel(NFC_BUF_TO_USER_DATA(chip->oob_poi +
 | |
| -					    layout->oobfree[i].offset),
 | |
| -		       nfc->regs + NFC_REG_USER_DATA_BASE);
 | |
| +		if (!oob_required)
 | |
| +			memset(oob_buf, 0xff, 4);
 | |
| +		else
 | |
| +			memcpy(oob_buf,
 | |
| +			       chip->oob_poi + layout->oobfree[i].offset,
 | |
| +			       4);
 | |
| +
 | |
| +
 | |
| +		memcpy_toio(nfc->regs + NFC_REG_USER_DATA_BASE, oob_buf, 4);
 | |
| +
 | |
| +		if (i) {
 | |
| +			cnt = ecc->bytes + 4;
 | |
| +			if (rnd &&
 | |
| +			    nand_rnd_is_activ(mtd, -1, offset, &cnt) > 0 &&
 | |
| +			    cnt == ecc->bytes + 4)
 | |
| +				rndactiv = true;
 | |
| +		} else {
 | |
| +			cnt = ecc->bytes + 2;
 | |
| +			if (rnd &&
 | |
| +			    nand_rnd_is_activ(mtd, -1, offset + 2, &cnt) > 0 &&
 | |
| +			    cnt == ecc->bytes + 2)
 | |
| +				rndactiv = true;
 | |
| +		}
 | |
| +
 | |
| +		if (rndactiv) {
 | |
| +			/* pre randomize to generate FF patterns on the NAND */
 | |
| +			if (!i) {
 | |
| +				u16 state = rnd->subseeds[rnd->page % rnd->nseeds];
 | |
| +				state = sunxi_nfc_hwrnd_single_step(state, 15);
 | |
| +				oob_buf[0] ^= state;
 | |
| +				state = sunxi_nfc_hwrnd_step(rnd, state, 1);
 | |
| +				oob_buf[1] ^= state;
 | |
| +				memcpy_toio(nfc->regs + NFC_REG_USER_DATA_BASE, oob_buf, 4);
 | |
| +			}
 | |
| +			tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
| +			tmp &= ~(NFC_RANDOM_DIRECTION | NFC_ECC_EXCEPTION);
 | |
| +			tmp |= NFC_RANDOM_EN;
 | |
| +			writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
| +		}
 | |
|  
 | |
|  		chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
 | |
|  
 | |
| @@ -669,6 +971,9 @@ static int sunxi_nfc_hw_ecc_write_page(s
 | |
|  		ret = sunxi_nfc_wait_int(nfc, NFC_CMD_INT_FLAG, 0);
 | |
|  		if (ret)
 | |
|  			return ret;
 | |
| +
 | |
| +		writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN,
 | |
| +		       nfc->regs + NFC_REG_ECC_CTL);
 | |
|  	}
 | |
|  
 | |
|  	if (oob_required) {
 | |
| @@ -677,11 +982,14 @@ static int sunxi_nfc_hw_ecc_write_page(s
 | |
|  			offset = mtd->writesize +
 | |
|  				 ecc->layout->oobfree[i].offset;
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
 | |
| +			nand_rnd_config(mtd, -1, offset, NAND_RND_WRITE);
 | |
|  			offset -= mtd->writesize;
 | |
| -			chip->write_buf(mtd, chip->oob_poi + offset, cnt);
 | |
| +			nand_rnd_write_buf(mtd, chip->oob_poi + offset, cnt);
 | |
|  		}
 | |
|  	}
 | |
|  
 | |
| +	nand_rnd_config(mtd, -1, -1, NAND_RND_WRITE);
 | |
| +
 | |
|  	tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
|  	tmp &= ~NFC_ECC_EN;
 | |
|  
 | |
| @@ -690,22 +998,76 @@ static int sunxi_nfc_hw_ecc_write_page(s
 | |
|  	return 0;
 | |
|  }
 | |
|  
 | |
| +static u16 sunxi_nfc_hw_ecc_rnd_steps(struct mtd_info *mtd, u16 state,
 | |
| +				      int column, int *left)
 | |
| +{
 | |
| +	struct nand_chip *chip = mtd->priv;
 | |
| +	struct nand_ecc_ctrl *ecc = chip->cur_ecc;
 | |
| +	struct sunxi_nand_hw_rnd *rnd = chip->cur_rnd->priv;
 | |
| +	int nblks = mtd->writesize / ecc->size;
 | |
| +	int modsize = ecc->size;
 | |
| +	int steps;
 | |
| +
 | |
| +	if (column < mtd->writesize) {
 | |
| +		steps = column % modsize;
 | |
| +		*left = modsize - steps;
 | |
| +	} else if (column < mtd->writesize +
 | |
| +			    (nblks * (ecc->bytes + 4))) {
 | |
| +		column -= mtd->writesize;
 | |
| +		steps = column % (ecc->bytes + 4);
 | |
| +		*left = ecc->bytes + 4 - steps;
 | |
| +		state = rnd->subseeds[rnd->page % rnd->nseeds];
 | |
| +	} else {
 | |
| +		steps = column % 4096;
 | |
| +		*left = mtd->writesize + mtd->oobsize - column;
 | |
| +	}
 | |
| +
 | |
| +	return sunxi_nfc_hwrnd_step(rnd, state, steps);
 | |
| +}
 | |
| +
 | |
|  static int sunxi_nfc_hw_syndrome_ecc_read_page(struct mtd_info *mtd,
 | |
|  					       struct nand_chip *chip,
 | |
|  					       uint8_t *buf, int oob_required,
 | |
|  					       int page)
 | |
|  {
 | |
|  	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
 | |
| +	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
 | |
|  	struct nand_ecc_ctrl *ecc = chip->cur_ecc;
 | |
|  	struct sunxi_nand_hw_ecc *data = ecc->priv;
 | |
|  	unsigned int max_bitflips = 0;
 | |
|  	uint8_t *oob = chip->oob_poi;
 | |
|  	int offset = 0;
 | |
|  	int ret;
 | |
| +	int status;
 | |
|  	int cnt;
 | |
|  	u32 tmp;
 | |
|  	int i;
 | |
|  
 | |
| +	status = nand_page_get_status(mtd, page);
 | |
| +	if (status == NAND_PAGE_STATUS_UNKNOWN) {
 | |
| +		chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
 | |
| +		sunxi_nfc_read_buf(mtd, sunxi_nand->buffer,
 | |
| +				   mtd->writesize + mtd->oobsize);
 | |
| +
 | |
| +		if (nand_page_is_empty(mtd, sunxi_nand->buffer,
 | |
| +				       sunxi_nand->buffer +
 | |
| +				       mtd->writesize)) {
 | |
| +			status = NAND_PAGE_EMPTY;
 | |
| +		} else {
 | |
| +			status = NAND_PAGE_FILLED;
 | |
| +			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0, -1);
 | |
| +		}
 | |
| +
 | |
| +		nand_page_set_status(mtd, page, status);
 | |
| +	}
 | |
| +
 | |
| +	if (status == NAND_PAGE_EMPTY) {
 | |
| +		memset(buf, 0xff, mtd->writesize);
 | |
| +		if (oob_required)
 | |
| +			memset(chip->oob_poi, 0xff, mtd->oobsize);
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
|  	tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
|  	tmp &= ~(NFC_ECC_MODE | NFC_ECC_PIPELINE | NFC_ECC_BLOCK_SIZE);
 | |
|  	tmp |= NFC_ECC_EN | (data->mode << NFC_ECC_MODE_SHIFT) |
 | |
| @@ -714,7 +1076,17 @@ static int sunxi_nfc_hw_syndrome_ecc_rea
 | |
|  	writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
|  
 | |
|  	for (i = 0; i < ecc->steps; i++) {
 | |
| -		chip->read_buf(mtd, NULL, ecc->size);
 | |
| +		nand_rnd_config(mtd, page, offset, NAND_RND_READ);
 | |
| +		nand_rnd_read_buf(mtd, NULL, ecc->size);
 | |
| +
 | |
| +		cnt = ecc->bytes + 4;
 | |
| +		if (nand_rnd_is_activ(mtd, page, offset, &cnt) > 0 &&
 | |
| +		    cnt == ecc->bytes + 4) {
 | |
| +			tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
| +			tmp &= ~(NFC_RANDOM_DIRECTION | NFC_ECC_EXCEPTION);
 | |
| +			tmp |= NFC_RANDOM_EN;
 | |
| +			writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
| +		}
 | |
|  
 | |
|  		tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | (1 << 30);
 | |
|  		writel(tmp, nfc->regs + NFC_REG_CMD);
 | |
| @@ -727,6 +1099,9 @@ static int sunxi_nfc_hw_syndrome_ecc_rea
 | |
|  		buf += ecc->size;
 | |
|  		offset += ecc->size;
 | |
|  
 | |
| +		writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN,
 | |
| +		       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
|  		if (readl(nfc->regs + NFC_REG_ECC_ST) & 0x1) {
 | |
|  			mtd->ecc_stats.failed++;
 | |
|  		} else {
 | |
| @@ -737,7 +1112,8 @@ static int sunxi_nfc_hw_syndrome_ecc_rea
 | |
|  
 | |
|  		if (oob_required) {
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
 | |
| -			chip->read_buf(mtd, oob, ecc->bytes + ecc->prepad);
 | |
| +			nand_rnd_config(mtd, -1, offset, NAND_RND_READ);
 | |
| +			nand_rnd_read_buf(mtd, oob, ecc->bytes + ecc->prepad);
 | |
|  			oob += ecc->bytes + ecc->prepad;
 | |
|  		}
 | |
|  
 | |
| @@ -748,10 +1124,13 @@ static int sunxi_nfc_hw_syndrome_ecc_rea
 | |
|  		cnt = mtd->oobsize - (oob - chip->oob_poi);
 | |
|  		if (cnt > 0) {
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDOUT, offset, -1);
 | |
| -			chip->read_buf(mtd, oob, cnt);
 | |
| +			nand_rnd_config(mtd, page, offset, NAND_RND_READ);
 | |
| +			nand_rnd_read_buf(mtd, oob, cnt);
 | |
|  		}
 | |
|  	}
 | |
|  
 | |
| +	nand_rnd_config(mtd, -1, -1, NAND_RND_READ);
 | |
| +
 | |
|  	writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_ECC_EN,
 | |
|  	       nfc->regs + NFC_REG_ECC_CTL);
 | |
|  
 | |
| @@ -766,6 +1145,7 @@ static int sunxi_nfc_hw_syndrome_ecc_wri
 | |
|  	struct sunxi_nfc *nfc = to_sunxi_nfc(chip->controller);
 | |
|  	struct nand_ecc_ctrl *ecc = chip->cur_ecc;
 | |
|  	struct sunxi_nand_hw_ecc *data = ecc->priv;
 | |
| +	struct sunxi_nand_hw_rnd *rnd = chip->cur_rnd->priv;
 | |
|  	uint8_t *oob = chip->oob_poi;
 | |
|  	int offset = 0;
 | |
|  	int ret;
 | |
| @@ -781,13 +1161,24 @@ static int sunxi_nfc_hw_syndrome_ecc_wri
 | |
|  	writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
|  
 | |
|  	for (i = 0; i < ecc->steps; i++) {
 | |
| -		chip->write_buf(mtd, buf + (i * ecc->size), ecc->size);
 | |
| +		nand_rnd_config(mtd, -1, offset, NAND_RND_WRITE);
 | |
| +		nand_rnd_write_buf(mtd, buf + (i * ecc->size), ecc->size);
 | |
|  		offset += ecc->size;
 | |
|  
 | |
|  		/* Fill OOB data in */
 | |
|  		writel(NFC_BUF_TO_USER_DATA(oob),
 | |
|  		       nfc->regs + NFC_REG_USER_DATA_BASE);
 | |
|  
 | |
| +		cnt = ecc->bytes + 4;
 | |
| +		if (rnd &&
 | |
| +		    nand_rnd_is_activ(mtd, rnd->page, offset, &cnt) > 0 &&
 | |
| +		    cnt == ecc->bytes + 4) {
 | |
| +			tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
| +			tmp &= ~(NFC_RANDOM_DIRECTION | NFC_ECC_EXCEPTION);
 | |
| +			tmp |= NFC_RANDOM_EN;
 | |
| +			writel(tmp, nfc->regs + NFC_REG_ECC_CTL);
 | |
| +		}
 | |
| +
 | |
|  		tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ACCESS_DIR |
 | |
|  		      (1 << 30);
 | |
|  		writel(tmp, nfc->regs + NFC_REG_CMD);
 | |
| @@ -796,6 +1187,9 @@ static int sunxi_nfc_hw_syndrome_ecc_wri
 | |
|  		if (ret)
 | |
|  			return ret;
 | |
|  
 | |
| +		writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN,
 | |
| +		       nfc->regs + NFC_REG_ECC_CTL);
 | |
| +
 | |
|  		offset += ecc->bytes + ecc->prepad;
 | |
|  		oob += ecc->bytes + ecc->prepad;
 | |
|  	}
 | |
| @@ -804,9 +1198,11 @@ static int sunxi_nfc_hw_syndrome_ecc_wri
 | |
|  		cnt = mtd->oobsize - (oob - chip->oob_poi);
 | |
|  		if (cnt > 0) {
 | |
|  			chip->cmdfunc(mtd, NAND_CMD_RNDIN, offset, -1);
 | |
| -			chip->write_buf(mtd, oob, cnt);
 | |
| +			nand_rnd_config(mtd, -1, offset, NAND_RND_WRITE);
 | |
| +			nand_rnd_write_buf(mtd, oob, cnt);
 | |
|  		}
 | |
|  	}
 | |
| +	nand_rnd_config(mtd, -1, -1, NAND_RND_WRITE);
 | |
|  
 | |
|  	tmp = readl(nfc->regs + NFC_REG_ECC_CTL);
 | |
|  	tmp &= ~NFC_ECC_EN;
 | |
| @@ -816,6 +1212,128 @@ static int sunxi_nfc_hw_syndrome_ecc_wri
 | |
|  	return 0;
 | |
|  }
 | |
|  
 | |
| +static u16 sunxi_nfc_hw_syndrome_ecc_rnd_steps(struct mtd_info *mtd, u16 state,
 | |
| +					       int column, int *left)
 | |
| +{
 | |
| +	struct nand_chip *chip = mtd->priv;
 | |
| +	struct nand_ecc_ctrl *ecc = chip->cur_ecc;
 | |
| +	struct sunxi_nand_hw_rnd *rnd = chip->cur_rnd->priv;
 | |
| +	int eccsteps = mtd->writesize / ecc->size;
 | |
| +	int modsize = ecc->size + ecc->prepad + ecc->bytes;
 | |
| +	int steps;
 | |
| +
 | |
| +	if (column < (eccsteps * modsize)) {
 | |
| +		steps = column % modsize;
 | |
| +		*left = modsize - steps;
 | |
| +		if (steps >= ecc->size) {
 | |
| +			steps -= ecc->size;
 | |
| +			state = rnd->subseeds[rnd->page % rnd->nseeds];
 | |
| +		}
 | |
| +	} else {
 | |
| +		steps = column % 4096;
 | |
| +		*left = mtd->writesize + mtd->oobsize - column;
 | |
| +	}
 | |
| +
 | |
| +	return sunxi_nfc_hwrnd_step(rnd, state, steps);
 | |
| +}
 | |
| +
 | |
| +static u16 default_seeds[] = {0x4a80};
 | |
| +
 | |
| +static void sunxi_nand_rnd_ctrl_cleanup(struct nand_rnd_ctrl *rnd)
 | |
| +{
 | |
| +	struct sunxi_nand_hw_rnd *hwrnd = rnd->priv;
 | |
| +
 | |
| +	if (hwrnd->seeds != default_seeds)
 | |
| +		kfree(hwrnd->seeds);
 | |
| +	kfree(hwrnd->subseeds);
 | |
| +	kfree(rnd->layout);
 | |
| +	kfree(hwrnd);
 | |
| +}
 | |
| +
 | |
| +static int sunxi_nand_rnd_ctrl_init(struct mtd_info *mtd,
 | |
| +				    struct nand_rnd_ctrl *rnd,
 | |
| +				    struct nand_ecc_ctrl *ecc,
 | |
| +				    struct device_node *np)
 | |
| +{
 | |
| +	struct sunxi_nand_hw_rnd *hwrnd;
 | |
| +	struct nand_rnd_layout *layout = NULL;
 | |
| +	int ret;
 | |
| +
 | |
| +	hwrnd = kzalloc(sizeof(*hwrnd), GFP_KERNEL);
 | |
| +	if (!hwrnd)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	hwrnd->seeds = default_seeds;
 | |
| +	hwrnd->nseeds = ARRAY_SIZE(default_seeds);
 | |
| +
 | |
| +	if (of_get_property(np, "nand-randomizer-seeds", &ret)) {
 | |
| +		hwrnd->nseeds = ret / sizeof(*hwrnd->seeds);
 | |
| +		hwrnd->seeds = kzalloc(hwrnd->nseeds * sizeof(*hwrnd->seeds),
 | |
| +				       GFP_KERNEL);
 | |
| +		if (!hwrnd->seeds) {
 | |
| +			ret = -ENOMEM;
 | |
| +			goto err;
 | |
| +		}
 | |
| +
 | |
| +		ret = of_property_read_u16_array(np, "nand-randomizer-seeds",
 | |
| +						 hwrnd->seeds, hwrnd->nseeds);
 | |
| +		if (ret)
 | |
| +			goto err;
 | |
| +	}
 | |
| +
 | |
| +	switch (ecc->mode) {
 | |
| +	case NAND_ECC_HW_SYNDROME:
 | |
| +		hwrnd->step = sunxi_nfc_hw_syndrome_ecc_rnd_steps;
 | |
| +		break;
 | |
| +
 | |
| +	case NAND_ECC_HW:
 | |
| +		hwrnd->step = sunxi_nfc_hw_ecc_rnd_steps;
 | |
| +
 | |
| +	default:
 | |
| +		layout = kzalloc(sizeof(*layout) + sizeof(struct nand_rndfree),
 | |
| +				 GFP_KERNEL);
 | |
| +		if (!layout) {
 | |
| +			ret = -ENOMEM;
 | |
| +			goto err;
 | |
| +		}
 | |
| +		layout->nranges = 1;
 | |
| +		layout->ranges[0].offset = mtd->writesize;
 | |
| +		layout->ranges[0].length = 2;
 | |
| +		rnd->layout = layout;
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	if (ecc->mode == NAND_ECC_HW_SYNDROME || ecc->mode == NAND_ECC_HW) {
 | |
| +		int i;
 | |
| +
 | |
| +		hwrnd->subseeds = kzalloc(hwrnd->nseeds *
 | |
| +					  sizeof(*hwrnd->subseeds),
 | |
| +					  GFP_KERNEL);
 | |
| +		if (!hwrnd->subseeds) {
 | |
| +			ret = -ENOMEM;
 | |
| +			goto err;
 | |
| +		}
 | |
| +
 | |
| +		for (i = 0; i < hwrnd->nseeds; i++)
 | |
| +			hwrnd->subseeds[i] = sunxi_nfc_hwrnd_step(hwrnd,
 | |
| +							hwrnd->seeds[i],
 | |
| +							ecc->size);
 | |
| +	}
 | |
| +
 | |
| +	rnd->config = sunxi_nfc_hwrnd_config;
 | |
| +	rnd->read_buf = sunxi_nfc_hwrnd_read_buf;
 | |
| +	rnd->write_buf = sunxi_nfc_hwrnd_write_buf;
 | |
| +	rnd->priv = hwrnd;
 | |
| +
 | |
| +	return 0;
 | |
| +
 | |
| +err:
 | |
| +	kfree(hwrnd);
 | |
| +	kfree(layout);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
|  static int sunxi_nand_chip_set_timings(struct sunxi_nand_chip *chip,
 | |
|  				       const struct nand_sdr_timings *timings)
 | |
|  {
 | |
| @@ -1076,6 +1594,40 @@ static int sunxi_nand_hw_syndrome_ecc_ct
 | |
|  	return 0;
 | |
|  }
 | |
|  
 | |
| +static void sunxi_nand_rnd_cleanup(struct nand_rnd_ctrl *rnd)
 | |
| +{
 | |
| +	switch (rnd->mode) {
 | |
| +	case NAND_RND_HW:
 | |
| +		sunxi_nand_rnd_ctrl_cleanup(rnd);
 | |
| +		break;
 | |
| +	default:
 | |
| +		break;
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static int sunxi_nand_rnd_init(struct mtd_info *mtd,
 | |
| +			       struct nand_rnd_ctrl *rnd,
 | |
| +			       struct nand_ecc_ctrl *ecc,
 | |
| +			       struct device_node *np)
 | |
| +{
 | |
| +	int ret;
 | |
| +
 | |
| +	rnd->mode = NAND_RND_NONE;
 | |
| +
 | |
| +	ret = of_get_nand_rnd_mode(np);
 | |
| +	if (ret >= 0)
 | |
| +		rnd->mode = ret;
 | |
| +
 | |
| +	switch (rnd->mode) {
 | |
| +	case NAND_RND_HW:
 | |
| +		return sunxi_nand_rnd_ctrl_init(mtd, rnd, ecc, np);
 | |
| +	default:
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
|  static void sunxi_nand_ecc_cleanup(struct nand_ecc_ctrl *ecc)
 | |
|  {
 | |
|  	switch (ecc->mode) {
 | |
| @@ -1167,7 +1719,14 @@ struct nand_part *sunxi_ofnandpart_parse
 | |
|  	if (ret)
 | |
|  		goto err;
 | |
|  
 | |
| +	ret = sunxi_nand_rnd_init(master, &part->rnd, &part->ecc, pp);
 | |
| +	if (ret) {
 | |
| +		sunxi_nand_ecc_cleanup(&part->ecc);
 | |
| +		goto err;
 | |
| +	}
 | |
| +
 | |
|  	part->part.ecc = &part->ecc;
 | |
| +	part->part.rnd = &part->rnd;
 | |
|  
 | |
|  	return &part->part;
 | |
|  
 | |
| @@ -1292,18 +1851,30 @@ static int sunxi_nand_chip_init(struct d
 | |
|  	if (ret)
 | |
|  		return ret;
 | |
|  
 | |
| +	chip->buffer = kzalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL);
 | |
| +	if (!chip->buffer)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
|  	ret = sunxi_nand_chip_init_timings(chip, np);
 | |
|  	if (ret) {
 | |
|  		dev_err(dev, "could not configure chip timings: %d\n", ret);
 | |
|  		return ret;
 | |
|  	}
 | |
|  
 | |
| +	ret = nand_pst_create(mtd);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
|  	ret = sunxi_nand_ecc_init(mtd, &nand->ecc, np);
 | |
|  	if (ret) {
 | |
|  		dev_err(dev, "ECC init failed: %d\n", ret);
 | |
|  		return ret;
 | |
|  	}
 | |
|  
 | |
| +	ret = sunxi_nand_rnd_init(mtd, &nand->rnd, &nand->ecc, np);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
|  	ret = nand_scan_tail(mtd);
 | |
|  	if (ret) {
 | |
|  		dev_err(dev, "nand_scan_tail failed: %d\n", ret);
 | |
| @@ -1360,6 +1931,8 @@ static void sunxi_nand_chips_cleanup(str
 | |
|  		nand_release(&chip->mtd);
 | |
|  		sunxi_nand_ecc_cleanup(&chip->nand.ecc);
 | |
|  		list_del(&chip->node);
 | |
| +		sunxi_nand_rnd_cleanup(&chip->nand.rnd);
 | |
| +		kfree(chip->buffer);
 | |
|  	}
 | |
|  }
 | |
|  
 |