// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright ( C ) 2013 Boris BREZILLON < b . brezillon . dev @ gmail . com >
*
* Derived from :
* https : //github.com/yuq/sunxi-nfc-mtd
* Copyright ( C ) 2013 Qiang Yu < yuq825 @ gmail . com >
*
* https : //github.com/hno/Allwinner-Info
* Copyright ( C ) 2013 Henrik Nordstr ö m < Henrik Nordstr ö m >
*
* Copyright ( C ) 2013 Dmitriy B . < rzk333 @ gmail . com >
* Copyright ( C ) 2013 Sergey Lapin < slapin @ ossfans . org >
*/
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/rawnand.h>
#include <linux/mtd/partitions.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/reset.h>
#define NFC_REG_CTL 0 x0000
#define NFC_REG_ST 0 x0004
#define NFC_REG_INT 0 x0008
#define NFC_REG_TIMING_CTL 0 x000C
#define NFC_REG_TIMING_CFG 0 x0010
#define NFC_REG_ADDR_LOW 0 x0014
#define NFC_REG_ADDR_HIGH 0 x0018
#define NFC_REG_SECTOR_NUM 0 x001C
#define NFC_REG_CNT 0 x0020
#define NFC_REG_CMD 0 x0024
#define NFC_REG_RCMD_SET 0 x0028
#define NFC_REG_WCMD_SET 0 x002C
#define NFC_REG_A10_IO_DATA 0 x0030
#define NFC_REG_A23_IO_DATA 0 x0300
#define NFC_REG_ECC_CTL 0 x0034
#define NFC_REG_ECC_ST 0 x0038
#define NFC_REG_DEBUG 0 x003C
#define NFC_REG_ECC_ERR_CNT(x) ((0 x0040 + (x)) & ~0 x3)
#define NFC_REG_USER_DATA(x) (0 x0050 + ((x) * 4 ))
#define NFC_REG_SPARE_AREA 0 x00A0
#define NFC_REG_PAT_ID 0 x00A4
#define NFC_REG_MDMA_ADDR 0 x00C0
#define NFC_REG_MDMA_CNT 0 x00C4
#define NFC_RAM0_BASE 0 x0400
#define NFC_RAM1_BASE 0 x0800
/* define bit use in NFC_CTL */
#define NFC_EN BIT(0 )
#define NFC_RESET BIT(1 )
#define NFC_BUS_WIDTH_MSK BIT(2 )
#define NFC_BUS_WIDTH_8 (0 << 2 )
#define NFC_BUS_WIDTH_16 (1 << 2 )
#define NFC_RB_SEL_MSK BIT(3 )
#define NFC_RB_SEL(x) ((x) << 3 )
#define NFC_CE_SEL_MSK GENMASK(26 , 24 )
#define NFC_CE_SEL(x) ((x) << 24 )
#define NFC_CE_CTL BIT(6 )
#define NFC_PAGE_SHIFT_MSK GENMASK(11 , 8 )
#define NFC_PAGE_SHIFT(x) (((x) < 10 ? 0 : (x) - 10 ) << 8 )
#define NFC_SAM BIT(12 )
#define NFC_RAM_METHOD BIT(14 )
#define NFC_DMA_TYPE_NORMAL BIT(15 )
#define NFC_DEBUG_CTL BIT(31 )
/* define bit use in NFC_ST */
#define NFC_RB_B2R BIT(0 )
#define NFC_CMD_INT_FLAG BIT(1 )
#define NFC_DMA_INT_FLAG BIT(2 )
#define NFC_CMD_FIFO_STATUS BIT(3 )
#define NFC_STA BIT(4 )
#define NFC_NATCH_INT_FLAG BIT(5 )
#define NFC_RB_STATE(x) BIT(x + 8 )
/* define bit use in NFC_INT */
#define NFC_B2R_INT_ENABLE BIT(0 )
#define NFC_CMD_INT_ENABLE BIT(1 )
#define NFC_DMA_INT_ENABLE BIT(2 )
#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \
NFC_CMD_INT_ENABLE | \
NFC_DMA_INT_ENABLE)
/* define bit use in NFC_TIMING_CTL */
#define NFC_TIMING_CTL_EDO BIT(8 )
/* define NFC_TIMING_CFG register layout */
#define NFC_TIMING_CFG(tWB, tADL, tWHR, tRHW, tCAD) \
(((tWB) & 0 x3) | (((tADL) & 0 x3) << 2 ) | \
(((tWHR) & 0 x3) << 4 ) | (((tRHW) & 0 x3) << 6 ) | \
(((tCAD) & 0 x7) << 8 ))
/* define bit use in NFC_CMD */
#define NFC_CMD_LOW_BYTE_MSK GENMASK(7 , 0 )
#define NFC_CMD_HIGH_BYTE_MSK GENMASK(15 , 8 )
#define NFC_CMD(x) (x)
#define NFC_ADR_NUM_MSK GENMASK(18 , 16 )
#define NFC_ADR_NUM(x) (((x) - 1 ) << 16 )
#define NFC_SEND_ADR BIT(19 )
#define NFC_ACCESS_DIR BIT(20 )
#define NFC_DATA_TRANS BIT(21 )
#define NFC_SEND_CMD1 BIT(22 )
#define NFC_WAIT_FLAG BIT(23 )
#define NFC_SEND_CMD2 BIT(24 )
#define NFC_SEQ BIT(25 )
#define NFC_DATA_SWAP_METHOD BIT(26 )
#define NFC_ROW_AUTO_INC BIT(27 )
#define NFC_SEND_CMD3 BIT(28 )
#define NFC_SEND_CMD4 BIT(29 )
#define NFC_CMD_TYPE_MSK GENMASK(31 , 30 )
#define NFC_NORMAL_OP (0 << 30 )
#define NFC_ECC_OP (1 << 30 )
#define NFC_PAGE_OP (2 U << 30 )
/* define bit use in NFC_RCMD_SET */
#define NFC_READ_CMD_MSK GENMASK(7 , 0 )
#define NFC_RND_READ_CMD0_MSK GENMASK(15 , 8 )
#define NFC_RND_READ_CMD1_MSK GENMASK(23 , 16 )
/* define bit use in NFC_WCMD_SET */
#define NFC_PROGRAM_CMD_MSK GENMASK(7 , 0 )
#define NFC_RND_WRITE_CMD_MSK GENMASK(15 , 8 )
#define NFC_READ_CMD0_MSK GENMASK(23 , 16 )
#define NFC_READ_CMD1_MSK GENMASK(31 , 24 )
/* define bit use in NFC_ECC_CTL */
#define NFC_ECC_EN BIT(0 )
#define NFC_ECC_PIPELINE BIT(3 )
#define NFC_ECC_EXCEPTION BIT(4 )
#define NFC_ECC_BLOCK_SIZE_MSK BIT(5 )
#define NFC_ECC_BLOCK_512 BIT(5 )
#define NFC_RANDOM_EN BIT(9 )
#define NFC_RANDOM_DIRECTION BIT(10 )
#define NFC_ECC_MODE_MSK GENMASK(15 , 12 )
#define NFC_ECC_MODE(x) ((x) << 12 )
#define NFC_RANDOM_SEED_MSK GENMASK(30 , 16 )
#define NFC_RANDOM_SEED(x) ((x) << 16 )
/* define bit use in NFC_ECC_ST */
#define NFC_ECC_ERR(x) BIT(x)
#define NFC_ECC_ERR_MSK GENMASK(15 , 0 )
#define NFC_ECC_PAT_FOUND(x) BIT(x + 16 )
#define NFC_ECC_ERR_CNT(b, x) (((x) >> (((b) % 4 ) * 8 )) & 0 xff)
#define NFC_DEFAULT_TIMEOUT_MS 1000
#define NFC_SRAM_SIZE 1024
#define NFC_MAX_CS 7
/**
* struct sunxi_nand_chip_sel - stores information related to NAND Chip Select
*
* @ cs : the NAND CS id used to communicate with a NAND Chip
* @ rb : the Ready / Busy pin ID . - 1 means no R / B pin connected to the NFC
*/
struct sunxi_nand_chip_sel {
u8 cs;
s8 rb;
};
/**
* struct sunxi_nand_hw_ecc - stores information related to HW ECC support
*
* @ ecc_ctl : ECC_CTL register value for this NAND chip
*/
struct sunxi_nand_hw_ecc {
u32 ecc_ctl;
};
/**
* struct sunxi_nand_chip - stores NAND chip device related information
*
* @ node : used to store NAND chips into a list
* @ nand : base NAND chip structure
* @ ecc : ECC controller structure
* @ clk_rate : clk_rate required for this NAND chip
* @ timing_cfg : TIMING_CFG register value for this NAND chip
* @ timing_ctl : TIMING_CTL register value for this NAND chip
* @ nsels : number of CS lines required by the NAND chip
* @ sels : array of CS lines descriptions
*/
struct sunxi_nand_chip {
struct list_head node;
struct nand_chip nand;
struct sunxi_nand_hw_ecc ecc;
unsigned long clk_rate;
u32 timing_cfg;
u32 timing_ctl;
int nsels;
struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
};
static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
{
return container_of(nand, struct sunxi_nand_chip, nand);
}
/*
* NAND Controller capabilities structure : stores NAND controller capabilities
* for distinction between compatible strings .
*
* @ has_mdma : Use mbus dma mode , otherwise general dma
* through MBUS on A23 / A33 needs extra configuration .
* @ reg_io_data : I / O data register
* @ dma_maxburst : DMA maxburst
*/
struct sunxi_nfc_caps {
bool has_mdma;
unsigned int reg_io_data;
unsigned int dma_maxburst;
};
/**
* struct sunxi_nfc - stores sunxi NAND controller information
*
* @ controller : base controller structure
* @ dev : parent device ( used to print error messages )
* @ regs : NAND controller registers
* @ ahb_clk : NAND controller AHB clock
* @ mod_clk : NAND controller mod clock
* @ reset : NAND controller reset line
* @ assigned_cs : bitmask describing already assigned CS lines
* @ clk_rate : NAND controller current clock rate
* @ chips : a list containing all the NAND chips attached to this NAND
* controller
* @ complete : a completion object used to wait for NAND controller events
* @ dmac : the DMA channel attached to the NAND controller
* @ caps : NAND Controller capabilities
*/
struct sunxi_nfc {
struct nand_controller controller;
struct device *dev;
void __iomem *regs;
struct clk *ahb_clk;
struct clk *mod_clk;
struct reset_control *reset;
unsigned long assigned_cs;
unsigned long clk_rate;
struct list_head chips;
struct completion complete;
struct dma_chan *dmac;
const struct sunxi_nfc_caps *caps;
};
static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_controller *ctrl)
{
return container_of(ctrl, struct sunxi_nfc, controller);
}
static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
{
struct sunxi_nfc *nfc = dev_id;
u32 st = readl(nfc->regs + NFC_REG_ST);
u32 ien = readl(nfc->regs + NFC_REG_INT);
if (!(ien & st))
return IRQ_NONE;
if ((ien & st) == ien)
complete(&nfc->complete);
writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT);
return IRQ_HANDLED;
}
static int sunxi_nfc_wait_events(struct sunxi_nfc *nfc, u32 events,
bool use_polling, unsigned int timeout_ms)
{
int ret;
if (events & ~NFC_INT_MASK)
return -EINVAL;
if (!timeout_ms)
timeout_ms = NFC_DEFAULT_TIMEOUT_MS;
if (!use_polling) {
init_completion(&nfc->complete);
writel(events, nfc->regs + NFC_REG_INT);
ret = wait_for_completion_timeout(&nfc->complete,
msecs_to_jiffies(timeout_ms));
if (!ret)
ret = -ETIMEDOUT;
else
ret = 0 ;
writel(0 , nfc->regs + NFC_REG_INT);
} else {
u32 status;
ret = readl_poll_timeout(nfc->regs + NFC_REG_ST, status,
(status & events) == events, 1 ,
timeout_ms * 1000 );
}
writel(events & NFC_INT_MASK, nfc->regs + NFC_REG_ST);
if (ret)
dev_err(nfc->dev, "wait interrupt timedout\n" );
return ret;
}
static int sunxi_nfc_wait_cmd_fifo_empty(struct sunxi_nfc *nfc)
{
u32 status;
int ret;
ret = readl_poll_timeout(nfc->regs + NFC_REG_ST, status,
!(status & NFC_CMD_FIFO_STATUS), 1 ,
NFC_DEFAULT_TIMEOUT_MS * 1000 );
if (ret)
dev_err(nfc->dev, "wait for empty cmd FIFO timedout\n" );
return ret;
}
static int sunxi_nfc_rst(struct sunxi_nfc *nfc)
{
u32 ctl;
int ret;
writel(0 , nfc->regs + NFC_REG_ECC_CTL);
writel(NFC_RESET, nfc->regs + NFC_REG_CTL);
ret = readl_poll_timeout(nfc->regs + NFC_REG_CTL, ctl,
!(ctl & NFC_RESET), 1 ,
NFC_DEFAULT_TIMEOUT_MS * 1000 );
if (ret)
dev_err(nfc->dev, "wait for NAND controller reset timedout\n" );
return ret;
}
static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf,
int chunksize, int nchunks,
enum dma_data_direction ddir,
struct scatterlist *sg)
{
struct dma_async_tx_descriptor *dmad;
enum dma_transfer_direction tdir;
dma_cookie_t dmat;
int ret;
if (ddir == DMA_FROM_DEVICE)
tdir = DMA_DEV_TO_MEM;
else
tdir = DMA_MEM_TO_DEV;
sg_init_one(sg, buf, nchunks * chunksize);
ret = dma_map_sg(nfc->dev, sg, 1 , ddir);
if (!ret)
return -ENOMEM;
if (!nfc->caps->has_mdma) {
dmad = dmaengine_prep_slave_sg(nfc->dmac, sg, 1 , tdir, DMA_CTRL_ACK);
if (!dmad) {
ret = -EINVAL;
goto err_unmap_buf;
}
}
writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RAM_METHOD,
nfc->regs + NFC_REG_CTL);
writel(nchunks, nfc->regs + NFC_REG_SECTOR_NUM);
writel(chunksize, nfc->regs + NFC_REG_CNT);
if (nfc->caps->has_mdma) {
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_DMA_TYPE_NORMAL,
nfc->regs + NFC_REG_CTL);
writel(chunksize * nchunks, nfc->regs + NFC_REG_MDMA_CNT);
writel(sg_dma_address(sg), nfc->regs + NFC_REG_MDMA_ADDR);
} else {
dmat = dmaengine_submit(dmad);
ret = dma_submit_error(dmat);
if (ret)
goto err_clr_dma_flag;
}
return 0 ;
err_clr_dma_flag:
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_RAM_METHOD,
nfc->regs + NFC_REG_CTL);
err_unmap_buf:
dma_unmap_sg(nfc->dev, sg, 1 , ddir);
return ret;
}
static void sunxi_nfc_dma_op_cleanup(struct sunxi_nfc *nfc,
enum dma_data_direction ddir,
struct scatterlist *sg)
{
dma_unmap_sg(nfc->dev, sg, 1 , ddir);
writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_RAM_METHOD,
nfc->regs + NFC_REG_CTL);
}
static void sunxi_nfc_select_chip(struct nand_chip *nand, unsigned int cs)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
struct sunxi_nand_chip_sel *sel;
u32 ctl;
if (cs >= sunxi_nand->nsels)
return ;
ctl = readl(nfc->regs + NFC_REG_CTL) &
~(NFC_PAGE_SHIFT_MSK | NFC_CE_SEL_MSK | NFC_RB_SEL_MSK | NFC_EN);
sel = &sunxi_nand->sels[cs];
ctl |= NFC_CE_SEL(sel->cs) | NFC_EN | NFC_PAGE_SHIFT(nand->page_shift);
if (sel->rb >= 0 )
ctl |= NFC_RB_SEL(sel->rb);
writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
if (nfc->clk_rate != sunxi_nand->clk_rate) {
clk_set_rate(nfc->mod_clk, sunxi_nand->clk_rate);
nfc->clk_rate = sunxi_nand->clk_rate;
}
writel(sunxi_nand->timing_ctl, nfc->regs + NFC_REG_TIMING_CTL);
writel(sunxi_nand->timing_cfg, nfc->regs + NFC_REG_TIMING_CFG);
writel(ctl, nfc->regs + NFC_REG_CTL);
}
static void sunxi_nfc_read_buf(struct nand_chip *nand, uint8_t *buf, int len)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
int ret;
int cnt;
int offs = 0 ;
u32 tmp;
while (len > offs) {
bool poll = false ;
cnt = min(len - offs, NFC_SRAM_SIZE);
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
break ;
writel(cnt, nfc->regs + NFC_REG_CNT);
tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
writel(tmp, nfc->regs + NFC_REG_CMD);
/* Arbitrary limit for polling mode */
if (cnt < 64 )
poll = true ;
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0 );
if (ret)
break ;
if (buf)
memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE,
cnt);
offs += cnt;
}
}
static void sunxi_nfc_write_buf(struct nand_chip *nand, const uint8_t *buf,
int len)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
int ret;
int cnt;
int offs = 0 ;
u32 tmp;
while (len > offs) {
bool poll = false ;
cnt = min(len - offs, NFC_SRAM_SIZE);
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
break ;
writel(cnt, nfc->regs + NFC_REG_CNT);
memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt);
tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
NFC_ACCESS_DIR;
writel(tmp, nfc->regs + NFC_REG_CMD);
/* Arbitrary limit for polling mode */
if (cnt < 64 )
poll = true ;
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0 );
if (ret)
break ;
offs += cnt;
}
}
/* These seed values have been extracted from Allwinner's BSP */
static const u16 sunxi_nfc_randomizer_page_seeds[] = {
0 x2b75, 0 x0bd0, 0 x5ca3, 0 x62d1, 0 x1c93, 0 x07e9, 0 x2162, 0 x3a72,
0 x0d67, 0 x67f9, 0 x1be7, 0 x077d, 0 x032f, 0 x0dac, 0 x2716, 0 x2436,
0 x7922, 0 x1510, 0 x3860, 0 x5287, 0 x480f, 0 x4252, 0 x1789, 0 x5a2d,
0 x2a49, 0 x5e10, 0 x437f, 0 x4b4e, 0 x2f45, 0 x216e, 0 x5cb7, 0 x7130,
0 x2a3f, 0 x60e4, 0 x4dc9, 0 x0ef0, 0 x0f52, 0 x1bb9, 0 x6211, 0 x7a56,
0 x226d, 0 x4ea7, 0 x6f36, 0 x3692, 0 x38bf, 0 x0c62, 0 x05eb, 0 x4c55,
0 x60f4, 0 x728c, 0 x3b6f, 0 x2037, 0 x7f69, 0 x0936, 0 x651a, 0 x4ceb,
0 x6218, 0 x79f3, 0 x383f, 0 x18d9, 0 x4f05, 0 x5c82, 0 x2912, 0 x6f17,
0 x6856, 0 x5938, 0 x1007, 0 x61ab, 0 x3e7f, 0 x57c2, 0 x542f, 0 x4f62,
0 x7454, 0 x2eac, 0 x7739, 0 x42d4, 0 x2f90, 0 x435a, 0 x2e52, 0 x2064,
0 x637c, 0 x66ad, 0 x2c90, 0 x0bad, 0 x759c, 0 x0029, 0 x0986, 0 x7126,
0 x1ca7, 0 x1605, 0 x386a, 0 x27f5, 0 x1380, 0 x6d75, 0 x24c3, 0 x0f8e,
0 x2b7a, 0 x1418, 0 x1fd1, 0 x7dc1, 0 x2d8e, 0 x43af, 0 x2267, 0 x7da3,
0 x4e3d, 0 x1338, 0 x50db, 0 x454d, 0 x764d, 0 x40a3, 0 x42e6, 0 x262b,
0 x2d2e, 0 x1aea, 0 x2e17, 0 x173d, 0 x3a6e, 0 x71bf, 0 x25f9, 0 x0a5d,
0 x7c57, 0 x0fbe, 0 x46ce, 0 x4939, 0 x6b17, 0 x37bb, 0 x3e91, 0 x76db,
};
/*
* sunxi_nfc_randomizer_ecc512_seeds and sunxi_nfc_randomizer_ecc1024_seeds
* have been generated using
* sunxi_nfc_randomizer_step ( seed , ( step_size * 8 ) + 15 ) , which is what
* the randomizer engine does internally before de / scrambling OOB data .
*
* Those tables are statically defined to avoid calculating randomizer state
* at runtime .
*/
static const u16 sunxi_nfc_randomizer_ecc512_seeds[] = {
0 x3346, 0 x367f, 0 x1f18, 0 x769a, 0 x4f64, 0 x068c, 0 x2ef1, 0 x6b64,
0 x28a9, 0 x15d7, 0 x30f8, 0 x3659, 0 x53db, 0 x7c5f, 0 x71d4, 0 x4409,
0 x26eb, 0 x03cc, 0 x655d, 0 x47d4, 0 x4daa, 0 x0877, 0 x712d, 0 x3617,
0 x3264, 0 x49aa, 0 x7f9e, 0 x588e, 0 x4fbc, 0 x7176, 0 x7f91, 0 x6c6d,
0 x4b95, 0 x5fb7, 0 x3844, 0 x4037, 0 x0184, 0 x081b, 0 x0ee8, 0 x5b91,
0 x293d, 0 x1f71, 0 x0e6f, 0 x402b, 0 x5122, 0 x1e52, 0 x22be, 0 x3d2d,
0 x75bc, 0 x7c60, 0 x6291, 0 x1a2f, 0 x61d4, 0 x74aa, 0 x4140, 0 x29ab,
0 x472d, 0 x2852, 0 x017e, 0 x15e8, 0 x5ec2, 0 x17cf, 0 x7d0f, 0 x06b8,
0 x117a, 0 x6b94, 0 x789b, 0 x3126, 0 x6ac5, 0 x5be7, 0 x150f, 0 x51f8,
0 x7889, 0 x0aa5, 0 x663d, 0 x77e8, 0 x0b87, 0 x3dcb, 0 x360d, 0 x218b,
0 x512f, 0 x7dc9, 0 x6a4d, 0 x630a, 0 x3547, 0 x1dd2, 0 x5aea, 0 x69a5,
0 x7bfa, 0 x5e4f, 0 x1519, 0 x6430, 0 x3a0e, 0 x5eb3, 0 x5425, 0 x0c7a,
0 x5540, 0 x3670, 0 x63c1, 0 x31e9, 0 x5a39, 0 x2de7, 0 x5979, 0 x2891,
0 x1562, 0 x014b, 0 x5b05, 0 x2756, 0 x5a34, 0 x13aa, 0 x6cb5, 0 x2c36,
0 x5e72, 0 x1306, 0 x0861, 0 x15ef, 0 x1ee8, 0 x5a37, 0 x7ac4, 0 x45dd,
0 x44c4, 0 x7266, 0 x2f41, 0 x3ccc, 0 x045e, 0 x7d40, 0 x7c66, 0 x0fa0,
};
static const u16 sunxi_nfc_randomizer_ecc1024_seeds[] = {
0 x2cf5, 0 x35f1, 0 x63a4, 0 x5274, 0 x2bd2, 0 x778b, 0 x7285, 0 x32b6,
0 x6a5c, 0 x70d6, 0 x757d, 0 x6769, 0 x5375, 0 x1e81, 0 x0cf3, 0 x3982,
0 x6787, 0 x042a, 0 x6c49, 0 x1925, 0 x56a8, 0 x40a9, 0 x063e, 0 x7bd9,
0 x4dbf, 0 x55ec, 0 x672e, 0 x7334, 0 x5185, 0 x4d00, 0 x232a, 0 x7e07,
0 x445d, 0 x6b92, 0 x528f, 0 x4255, 0 x53ba, 0 x7d82, 0 x2a2e, 0 x3a4e,
0 x75eb, 0 x450c, 0 x6844, 0 x1b5d, 0 x581a, 0 x4cc6, 0 x0379, 0 x37b2,
0 x419f, 0 x0e92, 0 x6b27, 0 x5624, 0 x01e3, 0 x07c1, 0 x44a5, 0 x130c,
0 x13e8, 0 x5910, 0 x0876, 0 x60c5, 0 x54e3, 0 x5b7f, 0 x2269, 0 x509f,
0 x7665, 0 x36fd, 0 x3e9a, 0 x0579, 0 x6295, 0 x14ef, 0 x0a81, 0 x1bcc,
0 x4b16, 0 x64db, 0 x0514, 0 x4f07, 0 x0591, 0 x3576, 0 x6853, 0 x0d9e,
0 x259f, 0 x38b7, 0 x64fb, 0 x3094, 0 x4693, 0 x6ddd, 0 x29bb, 0 x0bc8,
0 x3f47, 0 x490e, 0 x0c0e, 0 x7933, 0 x3c9e, 0 x5840, 0 x398d, 0 x3e68,
0 x4af1, 0 x71f5, 0 x57cf, 0 x1121, 0 x64eb, 0 x3579, 0 x15ac, 0 x584d,
0 x5f2a, 0 x47e2, 0 x6528, 0 x6eac, 0 x196e, 0 x6b96, 0 x0450, 0 x0179,
0 x609c, 0 x06e1, 0 x4626, 0 x42c7, 0 x273e, 0 x486f, 0 x0705, 0 x1601,
0 x145b, 0 x407e, 0 x062b, 0 x57a5, 0 x53f9, 0 x5659, 0 x4410, 0 x3ccd,
};
static u16 sunxi_nfc_randomizer_step(u16 state, int count)
{
state &= 0 x7fff;
/*
* This loop is just a simple implementation of a Fibonacci LFSR using
* the x16 + x15 + 1 polynomial .
*/
while (count--)
state = ((state >> 1 ) |
(((state ^ (state >> 1 )) & 1 ) << 14 )) & 0 x7fff;
return state;
}
static u16 sunxi_nfc_randomizer_state(struct nand_chip *nand, int page,
bool ecc)
{
struct mtd_info *mtd = nand_to_mtd(nand);
const u16 *seeds = sunxi_nfc_randomizer_page_seeds;
int mod = mtd_div_by_ws(mtd->erasesize, mtd);
if (mod > ARRAY_SIZE(sunxi_nfc_randomizer_page_seeds))
mod = ARRAY_SIZE(sunxi_nfc_randomizer_page_seeds);
if (ecc) {
if (mtd->ecc_step_size == 512 )
seeds = sunxi_nfc_randomizer_ecc512_seeds;
else
seeds = sunxi_nfc_randomizer_ecc1024_seeds;
}
return seeds[page % mod];
}
static void sunxi_nfc_randomizer_config(struct nand_chip *nand, int page,
bool ecc)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u32 ecc_ctl = readl(nfc->regs + NFC_REG_ECC_CTL);
u16 state;
if (!(nand->options & NAND_NEED_SCRAMBLING))
return ;
ecc_ctl = readl(nfc->regs + NFC_REG_ECC_CTL);
state = sunxi_nfc_randomizer_state(nand, page, ecc);
ecc_ctl = readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_SEED_MSK;
writel(ecc_ctl | NFC_RANDOM_SEED(state), nfc->regs + NFC_REG_ECC_CTL);
}
static void sunxi_nfc_randomizer_enable(struct nand_chip *nand)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
if (!(nand->options & NAND_NEED_SCRAMBLING))
return ;
writel(readl(nfc->regs + NFC_REG_ECC_CTL) | NFC_RANDOM_EN,
nfc->regs + NFC_REG_ECC_CTL);
}
static void sunxi_nfc_randomizer_disable(struct nand_chip *nand)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
if (!(nand->options & NAND_NEED_SCRAMBLING))
return ;
writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN,
nfc->regs + NFC_REG_ECC_CTL);
}
static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm)
{
u16 state = sunxi_nfc_randomizer_state(nand, page, true );
bbm[0 ] ^= state;
bbm[1 ] ^= sunxi_nfc_randomizer_step(state, 8 );
}
static void sunxi_nfc_randomizer_write_buf(struct nand_chip *nand,
const uint8_t *buf, int len,
bool ecc, int page)
{
sunxi_nfc_randomizer_config(nand, page, ecc);
sunxi_nfc_randomizer_enable(nand);
sunxi_nfc_write_buf(nand, buf, len);
sunxi_nfc_randomizer_disable(nand);
}
static void sunxi_nfc_randomizer_read_buf(struct nand_chip *nand, uint8_t *buf,
int len, bool ecc, int page)
{
sunxi_nfc_randomizer_config(nand, page, ecc);
sunxi_nfc_randomizer_enable(nand);
sunxi_nfc_read_buf(nand, buf, len);
sunxi_nfc_randomizer_disable(nand);
}
static void sunxi_nfc_hw_ecc_enable(struct nand_chip *nand)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
writel(sunxi_nand->ecc.ecc_ctl, nfc->regs + NFC_REG_ECC_CTL);
}
static void sunxi_nfc_hw_ecc_disable(struct nand_chip *nand)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
writel(0 , nfc->regs + NFC_REG_ECC_CTL);
}
static inline void sunxi_nfc_user_data_to_buf(u32 user_data, u8 *buf)
{
buf[0 ] = user_data;
buf[1 ] = user_data >> 8 ;
buf[2 ] = user_data >> 16 ;
buf[3 ] = user_data >> 24 ;
}
static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
{
return buf[0 ] | (buf[1 ] << 8 ) | (buf[2 ] << 16 ) | (buf[3 ] << 24 );
}
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
int step, bool bbm, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(step)),
oob);
/* De-randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
sunxi_nfc_randomize_bbm(nand, page, oob);
}
static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
const u8 *oob, int step,
bool bbm, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u8 user_data[4 ];
/* Randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
memcpy(user_data, oob, sizeof (user_data));
sunxi_nfc_randomize_bbm(nand, page, user_data);
oob = user_data;
}
writel(sunxi_nfc_buf_to_user_data(oob),
nfc->regs + NFC_REG_USER_DATA(step));
}
static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
unsigned int *max_bitflips, int ret)
{
struct mtd_info *mtd = nand_to_mtd(nand);
if (ret < 0 ) {
mtd->ecc_stats.failed++;
} else {
mtd->ecc_stats.corrected += ret;
*max_bitflips = max_t(unsigned int , *max_bitflips, ret);
}
}
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
int step, u32 status, bool *erased)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct nand_ecc_ctrl *ecc = &nand->ecc;
u32 tmp;
*erased = false ;
if (status & NFC_ECC_ERR(step))
return -EBADMSG;
if (status & NFC_ECC_PAT_FOUND(step)) {
u8 pattern;
if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID) & 0 x1))) {
pattern = 0 x0;
} else {
pattern = 0 xff;
*erased = true ;
}
if (data)
memset(data, pattern, ecc->size);
if (oob)
memset(oob, pattern, ecc->bytes + 4 );
return 0 ;
}
tmp = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(step));
return NFC_ECC_ERR_CNT(step, tmp);
}
static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u8 *data, int data_off,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
bool bbm, bool oob_required, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int raw_mode = 0 ;
bool erased;
int ret;
if (*cur_off != data_off)
nand_change_read_column_op(nand, data_off, NULL, 0 , false );
sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false , page);
if (data_off + ecc->size != oob_off)
nand_change_read_column_op(nand, oob_off, NULL, 0 , false );
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
return ret;
sunxi_nfc_randomizer_config(nand, page, false );
sunxi_nfc_randomizer_enable(nand);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false , 0 );
sunxi_nfc_randomizer_disable(nand);
if (ret)
return ret;
*cur_off = oob_off + ecc->bytes + 4 ;
ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, 0 ,
readl(nfc->regs + NFC_REG_ECC_ST),
&erased);
if (erased)
return 1 ;
if (ret < 0 ) {
/*
* Re - read the data with the randomizer disabled to identify
* bitflips in erased pages .
*/
if (nand->options & NAND_NEED_SCRAMBLING)
nand_change_read_column_op(nand, data_off, data,
ecc->size, false );
else
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
ecc->size);
nand_change_read_column_op(nand, oob_off, oob, ecc->bytes + 4 ,
false );
ret = nand_check_erased_ecc_chunk(data, ecc->size,
oob, ecc->bytes + 4 ,
NULL, 0 , ecc->strength);
if (ret >= 0 )
raw_mode = 1 ;
} else {
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size);
if (oob_required) {
nand_change_read_column_op(nand, oob_off, NULL, 0 ,
false );
sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + 4 ,
true , page);
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, 0 ,
bbm, page);
}
}
sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret);
return raw_mode;
}
static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
u8 *oob, int *cur_off,
bool randomize, int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int offset = ((ecc->bytes + 4 ) * ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0 )
return ;
if (!cur_off || *cur_off != offset)
nand_change_read_column_op(nand, mtd->writesize, NULL, 0 ,
false );
if (!randomize)
sunxi_nfc_read_buf(nand, oob + offset, len);
else
sunxi_nfc_randomizer_read_buf(nand, oob + offset, len,
false , page);
if (cur_off)
*cur_off = mtd->oobsize + mtd->writesize;
}
static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf,
int oob_required, int page,
int nchunks)
{
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int max_bitflips = 0 ;
int ret, i, raw_mode = 0 ;
struct scatterlist sg;
u32 status, wait;
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
return ret;
ret = sunxi_nfc_dma_op_prepare(nfc, buf, ecc->size, nchunks,
DMA_FROM_DEVICE, &sg);
if (ret)
return ret;
sunxi_nfc_hw_ecc_enable(nand);
sunxi_nfc_randomizer_config(nand, page, false );
sunxi_nfc_randomizer_enable(nand);
writel((NAND_CMD_RNDOUTSTART << 16 ) | (NAND_CMD_RNDOUT << 8 ) |
NAND_CMD_READSTART, nfc->regs + NFC_REG_RCMD_SET);
wait = NFC_CMD_INT_FLAG;
if (nfc->caps->has_mdma)
wait |= NFC_DMA_INT_FLAG;
else
dma_async_issue_pending(nfc->dmac);
writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD | NFC_DATA_TRANS,
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, wait, false , 0 );
if (ret && !nfc->caps->has_mdma)
dmaengine_terminate_all(nfc->dmac);
sunxi_nfc_randomizer_disable(nand);
sunxi_nfc_hw_ecc_disable(nand);
sunxi_nfc_dma_op_cleanup(nfc, DMA_FROM_DEVICE, &sg);
if (ret)
return ret;
status = readl(nfc->regs + NFC_REG_ECC_ST);
for (i = 0 ; i < nchunks; i++) {
int data_off = i * ecc->size;
int oob_off = i * (ecc->bytes + 4 );
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
bool erased;
ret = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
i, status, &erased);
/* ECC errors are handled in the second loop. */
if (ret < 0 )
continue ;
if (oob_required && !erased) {
/* TODO: use DMA to retrieve OOB */
nand_change_read_column_op(nand,
mtd->writesize + oob_off,
oob, ecc->bytes + 4 , false );
sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i,
!i, page);
}
if (erased)
raw_mode = 1 ;
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
}
if (status & NFC_ECC_ERR_MSK) {
for (i = 0 ; i < nchunks; i++) {
int data_off = i * ecc->size;
int oob_off = i * (ecc->bytes + 4 );
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
if (!(status & NFC_ECC_ERR(i)))
continue ;
/*
* Re - read the data with the randomizer disabled to
* identify bitflips in erased pages .
* TODO : use DMA to read page in raw mode
*/
if (randomized)
nand_change_read_column_op(nand, data_off,
data, ecc->size,
false );
/* TODO: use DMA to retrieve OOB */
nand_change_read_column_op(nand,
mtd->writesize + oob_off,
oob, ecc->bytes + 4 , false );
ret = nand_check_erased_ecc_chunk(data, ecc->size,
oob, ecc->bytes + 4 ,
NULL, 0 ,
ecc->strength);
if (ret >= 0 )
raw_mode = 1 ;
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
}
}
if (oob_required)
sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi,
NULL, !raw_mode,
page);
return max_bitflips;
}
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
const u8 *data, int data_off,
const u8 *oob, int oob_off,
int *cur_off, bool bbm,
int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret;
if (data_off != *cur_off)
nand_change_write_column_op(nand, data_off, NULL, 0 , false );
sunxi_nfc_randomizer_write_buf(nand, data, ecc->size, false , page);
if (data_off + ecc->size != oob_off)
nand_change_write_column_op(nand, oob_off, NULL, 0 , false );
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
return ret;
sunxi_nfc_randomizer_config(nand, page, false );
sunxi_nfc_randomizer_enable(nand);
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0 , bbm, page);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
NFC_ACCESS_DIR | NFC_ECC_OP,
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false , 0 );
sunxi_nfc_randomizer_disable(nand);
if (ret)
return ret;
*cur_off = oob_off + ecc->bytes + 4 ;
return 0 ;
}
static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
u8 *oob, int *cur_off,
int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int offset = ((ecc->bytes + 4 ) * ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0 )
return ;
if (!cur_off || *cur_off != offset)
nand_change_write_column_op(nand, offset + mtd->writesize,
NULL, 0 , false );
sunxi_nfc_randomizer_write_buf(nand, oob + offset, len, false , page);
if (cur_off)
*cur_off = mtd->oobsize + mtd->writesize;
}
static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
int oob_required, int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int max_bitflips = 0 ;
int ret, i, cur_off = 0 ;
bool raw_mode = false ;
sunxi_nfc_select_chip(nand, nand->cur_cs);
nand_read_page_op(nand, page, 0 , NULL, 0 );
sunxi_nfc_hw_ecc_enable(nand);
for (i = 0 ; i < ecc->steps; i++) {
int data_off = i * ecc->size;
int oob_off = i * (ecc->bytes + 4 );
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips,
!i, oob_required, page);
if (ret < 0 )
return ret;
else if (ret)
raw_mode = true ;
}
if (oob_required)
sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off,
!raw_mode, page);
sunxi_nfc_hw_ecc_disable(nand);
return max_bitflips;
}
static int sunxi_nfc_hw_ecc_read_page_dma(struct nand_chip *nand, u8 *buf,
int oob_required, int page)
{
int ret;
sunxi_nfc_select_chip(nand, nand->cur_cs);
nand_read_page_op(nand, page, 0 , NULL, 0 );
ret = sunxi_nfc_hw_ecc_read_chunks_dma(nand, buf, oob_required, page,
nand->ecc.steps);
if (ret >= 0 )
return ret;
/* Fallback to PIO mode */
return sunxi_nfc_hw_ecc_read_page(nand, buf, oob_required, page);
}
static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
u32 data_offs, u32 readlen,
u8 *bufpoi, int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0 ;
unsigned int max_bitflips = 0 ;
sunxi_nfc_select_chip(nand, nand->cur_cs);
nand_read_page_op(nand, page, 0 , NULL, 0 );
sunxi_nfc_hw_ecc_enable(nand);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
int data_off = i * ecc->size;
int oob_off = i * (ecc->bytes + 4 );
u8 *data = bufpoi + data_off;
u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips, !i,
false , page);
if (ret < 0 )
return ret;
}
sunxi_nfc_hw_ecc_disable(nand);
return max_bitflips;
}
static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand,
u32 data_offs, u32 readlen,
u8 *buf, int page)
{
int nchunks = DIV_ROUND_UP(data_offs + readlen, nand->ecc.size);
int ret;
sunxi_nfc_select_chip(nand, nand->cur_cs);
nand_read_page_op(nand, page, 0 , NULL, 0 );
ret = sunxi_nfc_hw_ecc_read_chunks_dma(nand, buf, false , page, nchunks);
if (ret >= 0 )
return ret;
/* Fallback to PIO mode */
return sunxi_nfc_hw_ecc_read_subpage(nand, data_offs, readlen,
buf, page);
}
static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
const uint8_t *buf, int oob_required,
int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0 ;
sunxi_nfc_select_chip(nand, nand->cur_cs);
nand_prog_page_begin_op(nand, page, 0 , NULL, 0 );
sunxi_nfc_hw_ecc_enable(nand);
for (i = 0 ; i < ecc->steps; i++) {
int data_off = i * ecc->size;
int oob_off = i * (ecc->bytes + 4 );
const u8 *data = buf + data_off;
const u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, !i, page);
if (ret)
return ret;
}
if (oob_required || (nand->options & NAND_NEED_SCRAMBLING))
sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
&cur_off, page);
sunxi_nfc_hw_ecc_disable(nand);
return nand_prog_page_end_op(nand);
}
static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
u32 data_offs, u32 data_len,
const u8 *buf, int oob_required,
int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0 ;
sunxi_nfc_select_chip(nand, nand->cur_cs);
nand_prog_page_begin_op(nand, page, 0 , NULL, 0 );
sunxi_nfc_hw_ecc_enable(nand);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
int data_off = i * ecc->size;
int oob_off = i * (ecc->bytes + 4 );
const u8 *data = buf + data_off;
const u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, !i, page);
if (ret)
return ret;
}
sunxi_nfc_hw_ecc_disable(nand);
return nand_prog_page_end_op(nand);
}
static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
const u8 *buf,
int oob_required,
int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct nand_ecc_ctrl *ecc = &nand->ecc;
struct scatterlist sg;
u32 wait;
int ret, i;
sunxi_nfc_select_chip(nand, nand->cur_cs);
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
return ret;
ret = sunxi_nfc_dma_op_prepare(nfc, buf, ecc->size, ecc->steps,
DMA_TO_DEVICE, &sg);
if (ret)
goto pio_fallback;
for (i = 0 ; i < ecc->steps; i++) {
const u8 *oob = nand->oob_poi + (i * (ecc->bytes + 4 ));
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
}
nand_prog_page_begin_op(nand, page, 0 , NULL, 0 );
sunxi_nfc_hw_ecc_enable(nand);
sunxi_nfc_randomizer_config(nand, page, false );
sunxi_nfc_randomizer_enable(nand);
writel((NAND_CMD_RNDIN << 8 ) | NAND_CMD_PAGEPROG,
nfc->regs + NFC_REG_WCMD_SET);
wait = NFC_CMD_INT_FLAG;
if (nfc->caps->has_mdma)
wait |= NFC_DMA_INT_FLAG;
else
dma_async_issue_pending(nfc->dmac);
writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD |
NFC_DATA_TRANS | NFC_ACCESS_DIR,
nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, wait, false , 0 );
if (ret && !nfc->caps->has_mdma)
dmaengine_terminate_all(nfc->dmac);
sunxi_nfc_randomizer_disable(nand);
sunxi_nfc_hw_ecc_disable(nand);
sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg);
if (ret)
return ret;
if (oob_required || (nand->options & NAND_NEED_SCRAMBLING))
/* TODO: use DMA to transfer extra OOB bytes ? */
sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi,
NULL, page);
return nand_prog_page_end_op(nand);
pio_fallback:
return sunxi_nfc_hw_ecc_write_page(nand, buf, oob_required, page);
}
static int sunxi_nfc_hw_ecc_read_oob(struct nand_chip *nand, int page)
{
u8 *buf = nand_get_data_buf(nand);
return nand->ecc.read_page(nand, buf, 1 , page);
}
static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
u8 *buf = nand_get_data_buf(nand);
int ret;
memset(buf, 0 xff, mtd->writesize);
ret = nand->ecc.write_page(nand, buf, 1 , page);
if (ret)
return ret;
/* Send command to program the OOB data */
return nand_prog_page_end_op(nand);
}
static const s32 tWB_lut[] = {6 , 12 , 16 , 20 };
static const s32 tRHW_lut[] = {4 , 8 , 12 , 20 };
static int _sunxi_nand_lookup_timing(const s32 *lut, int lut_size, u32 duration,
u32 clk_period)
{
u32 clk_cycles = DIV_ROUND_UP(duration, clk_period);
int i;
for (i = 0 ; i < lut_size; i++) {
if (clk_cycles <= lut[i])
return i;
}
/* Doesn't fit */
return -EINVAL;
}
#define sunxi_nand_lookup_timing(l, p, c) \
_sunxi_nand_lookup_timing(l, ARRAY_SIZE(l), p, c)
static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
const struct nand_interface_config *conf)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
const struct nand_sdr_timings *timings;
u32 min_clk_period = 0 ;
s32 tWB, tADL, tWHR, tRHW, tCAD;
long real_clk_rate;
timings = nand_get_sdr_timings(conf);
if (IS_ERR(timings))
return -ENOTSUPP;
/* T1 <=> tCLS */
if (timings->tCLS_min > min_clk_period)
min_clk_period = timings->tCLS_min;
/* T2 <=> tCLH */
if (timings->tCLH_min > min_clk_period)
min_clk_period = timings->tCLH_min;
/* T3 <=> tCS */
if (timings->tCS_min > min_clk_period)
min_clk_period = timings->tCS_min;
/* T4 <=> tCH */
if (timings->tCH_min > min_clk_period)
min_clk_period = timings->tCH_min;
/* T5 <=> tWP */
if (timings->tWP_min > min_clk_period)
min_clk_period = timings->tWP_min;
/* T6 <=> tWH */
if (timings->tWH_min > min_clk_period)
min_clk_period = timings->tWH_min;
/* T7 <=> tALS */
if (timings->tALS_min > min_clk_period)
min_clk_period = timings->tALS_min;
/* T8 <=> tDS */
if (timings->tDS_min > min_clk_period)
min_clk_period = timings->tDS_min;
/* T9 <=> tDH */
if (timings->tDH_min > min_clk_period)
min_clk_period = timings->tDH_min;
/* T10 <=> tRR */
if (timings->tRR_min > (min_clk_period * 3 ))
min_clk_period = DIV_ROUND_UP(timings->tRR_min, 3 );
/* T11 <=> tALH */
if (timings->tALH_min > min_clk_period)
min_clk_period = timings->tALH_min;
/* T12 <=> tRP */
if (timings->tRP_min > min_clk_period)
min_clk_period = timings->tRP_min;
/* T13 <=> tREH */
if (timings->tREH_min > min_clk_period)
min_clk_period = timings->tREH_min;
/* T14 <=> tRC */
if (timings->tRC_min > (min_clk_period * 2 ))
min_clk_period = DIV_ROUND_UP(timings->tRC_min, 2 );
/* T15 <=> tWC */
if (timings->tWC_min > (min_clk_period * 2 ))
min_clk_period = DIV_ROUND_UP(timings->tWC_min, 2 );
/* T16 - T19 + tCAD */
if (timings->tWB_max > (min_clk_period * 20 ))
min_clk_period = DIV_ROUND_UP(timings->tWB_max, 20 );
if (timings->tADL_min > (min_clk_period * 32 ))
min_clk_period = DIV_ROUND_UP(timings->tADL_min, 32 );
if (timings->tWHR_min > (min_clk_period * 32 ))
min_clk_period = DIV_ROUND_UP(timings->tWHR_min, 32 );
if (timings->tRHW_min > (min_clk_period * 20 ))
min_clk_period = DIV_ROUND_UP(timings->tRHW_min, 20 );
/*
* In non - EDO , tREA should be less than tRP to guarantee that the
* controller does not sample the IO lines too early . Unfortunately ,
* the sunxi NAND controller does not allow us to have different
* values for tRP and tREH ( tRP = tREH = tRW / 2 ) .
*
* We have 2 options to overcome this limitation :
*
* 1 / Extend tRC to fulfil the tREA < = tRC / 2 constraint
* 2 / Use EDO mode ( only works if timings - > tRLOH > 0 )
*/
if (timings->tREA_max > min_clk_period && !timings->tRLOH_min)
min_clk_period = timings->tREA_max;
tWB = sunxi_nand_lookup_timing(tWB_lut, timings->tWB_max,
min_clk_period);
if (tWB < 0 ) {
dev_err(nfc->dev, "unsupported tWB\n" );
return tWB;
}
tADL = DIV_ROUND_UP(timings->tADL_min, min_clk_period) >> 3 ;
if (tADL > 3 ) {
dev_err(nfc->dev, "unsupported tADL\n" );
return -EINVAL;
}
tWHR = DIV_ROUND_UP(timings->tWHR_min, min_clk_period) >> 3 ;
if (tWHR > 3 ) {
dev_err(nfc->dev, "unsupported tWHR\n" );
return -EINVAL;
}
tRHW = sunxi_nand_lookup_timing(tRHW_lut, timings->tRHW_min,
min_clk_period);
if (tRHW < 0 ) {
dev_err(nfc->dev, "unsupported tRHW\n" );
return tRHW;
}
if (csline == NAND_DATA_IFACE_CHECK_ONLY)
return 0 ;
/*
* TODO : according to ONFI specs this value only applies for DDR NAND ,
* but Allwinner seems to set this to 0 x7 . Mimic them for now .
*/
tCAD = 0 x7;
/* TODO: A83 has some more bits for CDQSS, CS, CLHZ, CCS, WC */
sunxi_nand->timing_cfg = NFC_TIMING_CFG(tWB, tADL, tWHR, tRHW, tCAD);
/* Convert min_clk_period from picoseconds to nanoseconds */
min_clk_period = DIV_ROUND_UP(min_clk_period, 1000 );
/*
* Unlike what is stated in Allwinner datasheet , the clk_rate should
* be set to ( 1 / min_clk_period ) , and not ( 2 / min_clk_period ) .
* This new formula was verified with a scope and validated by
* Allwinner engineers .
*/
sunxi_nand->clk_rate = NSEC_PER_SEC / min_clk_period;
real_clk_rate = clk_round_rate(nfc->mod_clk, sunxi_nand->clk_rate);
if (real_clk_rate <= 0 ) {
dev_err(nfc->dev, "Unable to round clk %lu\n" ,
sunxi_nand->clk_rate);
return -EINVAL;
}
sunxi_nand->timing_ctl = 0 ;
/*
* ONFI specification 3 . 1 , paragraph 4 . 15 . 2 dictates that EDO data
* output cycle timings shall be used if the host drives tRC less than
* 30 ns . We should also use EDO mode if tREA is bigger than tRP .
*/
min_clk_period = NSEC_PER_SEC / real_clk_rate;
if (min_clk_period * 2 < 30 || min_clk_period * 1000 < timings->tREA_max)
sunxi_nand->timing_ctl = NFC_TIMING_CTL_EDO;
return 0 ;
}
static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *oobregion)
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
if (section >= ecc->steps)
return -ERANGE;
oobregion->offset = section * (ecc->bytes + 4 ) + 4 ;
oobregion->length = ecc->bytes;
return 0 ;
}
static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *oobregion)
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
if (section > ecc->steps)
return -ERANGE;
/*
* The first 2 bytes are used for BB markers , hence we
* only have 2 bytes available in the first user data
* section .
*/
if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) {
oobregion->offset = 2 ;
oobregion->length = 2 ;
return 0 ;
}
/*
* The controller does not provide access to OOB bytes
* past the end of the ECC data .
*/
if (section == ecc->steps && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST)
return -ERANGE;
oobregion->offset = section * (ecc->bytes + 4 );
if (section < ecc->steps)
oobregion->length = 4 ;
else
oobregion->length = mtd->oobsize - oobregion->offset;
return 0 ;
}
static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
.ecc = sunxi_nand_ooblayout_ecc,
.free = sunxi_nand_ooblayout_free,
};
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc,
struct device_node *np)
{
static const u8 strengths[] = { 16 , 24 , 28 , 32 , 40 , 48 , 56 , 60 , 64 };
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_device *nanddev = mtd_to_nanddev(mtd);
int nsectors;
int i;
if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
int bytes;
ecc->size = 1024 ;
nsectors = mtd->writesize / ecc->size;
/* Reserve 2 bytes for the BBM */
bytes = (mtd->oobsize - 2 ) / nsectors;
/* 4 non-ECC bytes are added before each ECC bytes section */
bytes -= 4 ;
/* and bytes has to be even. */
if (bytes % 2 )
bytes--;
ecc->strength = bytes * 8 / fls(8 * ecc->size);
for (i = 0 ; i < ARRAY_SIZE(strengths); i++) {
if (strengths[i] > ecc->strength)
break ;
}
if (!i)
ecc->strength = 0 ;
else
ecc->strength = strengths[i - 1 ];
}
if (ecc->size != 512 && ecc->size != 1024 )
return -EINVAL;
/* Prefer 1k ECC chunk over 512 ones */
if (ecc->size == 512 && mtd->writesize > 512 ) {
ecc->size = 1024 ;
ecc->strength *= 2 ;
}
/* Add ECC info retrieval from DT */
for (i = 0 ; i < ARRAY_SIZE(strengths); i++) {
if (ecc->strength <= strengths[i]) {
/*
* Update ecc - > strength value with the actual strength
* that will be used by the ECC engine .
*/
ecc->strength = strengths[i];
break ;
}
}
if (i >= ARRAY_SIZE(strengths)) {
dev_err(nfc->dev, "unsupported strength\n" );
return -ENOTSUPP;
}
/* HW ECC always request ECC bytes for 1024 bytes blocks */
ecc->bytes = DIV_ROUND_UP(ecc->strength * fls(8 * 1024 ), 8 );
/* HW ECC always work with even numbers of ECC bytes */
ecc->bytes = ALIGN(ecc->bytes, 2 );
nsectors = mtd->writesize / ecc->size;
if (mtd->oobsize < ((ecc->bytes + 4 ) * nsectors))
return -EINVAL;
ecc->read_oob = sunxi_nfc_hw_ecc_read_oob;
ecc->write_oob = sunxi_nfc_hw_ecc_write_oob;
mtd_set_ooblayout(mtd, &sunxi_nand_ooblayout_ops);
if (nfc->dmac || nfc->caps->has_mdma) {
ecc->read_page = sunxi_nfc_hw_ecc_read_page_dma;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage_dma;
ecc->write_page = sunxi_nfc_hw_ecc_write_page_dma;
nand->options |= NAND_USES_DMA;
} else {
ecc->read_page = sunxi_nfc_hw_ecc_read_page;
ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage;
ecc->write_page = sunxi_nfc_hw_ecc_write_page;
}
/* TODO: support DMA for raw accesses and subpage write */
ecc->write_subpage = sunxi_nfc_hw_ecc_write_subpage;
ecc->read_oob_raw = nand_read_oob_std;
ecc->write_oob_raw = nand_write_oob_std;
sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(i) | NFC_ECC_EXCEPTION |
NFC_ECC_PIPELINE | NFC_ECC_EN;
if (ecc->size == 512 )
sunxi_nand->ecc.ecc_ctl |= NFC_ECC_BLOCK_512;
return 0 ;
}
static int sunxi_nand_attach_chip(struct nand_chip *nand)
{
const struct nand_ecc_props *requirements =
nanddev_get_ecc_requirements(&nand->base);
struct nand_ecc_ctrl *ecc = &nand->ecc;
struct device_node *np = nand_get_flash_node(nand);
int ret;
if (nand->bbt_options & NAND_BBT_USE_FLASH)
nand->bbt_options |= NAND_BBT_NO_OOB;
if (nand->options & NAND_NEED_SCRAMBLING)
nand->options |= NAND_NO_SUBPAGE_WRITE;
nand->options |= NAND_SUBPAGE_READ;
if (!ecc->size) {
ecc->size = requirements->step_size;
ecc->strength = requirements->strength;
}
if (!ecc->size || !ecc->strength)
return -EINVAL;
switch (ecc->engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_HOST:
ret = sunxi_nand_hw_ecc_ctrl_init(nand, ecc, np);
if (ret)
return ret;
break ;
case NAND_ECC_ENGINE_TYPE_NONE:
case NAND_ECC_ENGINE_TYPE_SOFT:
break ;
default :
return -EINVAL;
}
return 0 ;
}
static int sunxi_nfc_exec_subop(struct nand_chip *nand,
const struct nand_subop *subop)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
u32 cmd = 0 , extcmd = 0 , cnt = 0 , addrs[2 ] = { };
unsigned int i, j, remaining, start;
void *inbuf = NULL;
int ret;
for (i = 0 ; i < subop->ninstrs; i++) {
const struct nand_op_instr *instr = &subop->instrs[i];
switch (instr->type) {
case NAND_OP_CMD_INSTR:
if (cmd & NFC_SEND_CMD1) {
if (WARN_ON(cmd & NFC_SEND_CMD2))
return -EINVAL;
cmd |= NFC_SEND_CMD2;
extcmd |= instr->ctx.cmd.opcode;
} else {
cmd |= NFC_SEND_CMD1 |
NFC_CMD(instr->ctx.cmd.opcode);
}
break ;
case NAND_OP_ADDR_INSTR:
remaining = nand_subop_get_num_addr_cyc(subop, i);
start = nand_subop_get_addr_start_off(subop, i);
for (j = 0 ; j < 8 && j + start < remaining; j++) {
u32 addr = instr->ctx.addr.addrs[j + start];
addrs[j / 4 ] |= addr << (j % 4 ) * 8 ;
}
if (j)
cmd |= NFC_SEND_ADR | NFC_ADR_NUM(j);
break ;
case NAND_OP_DATA_IN_INSTR:
case NAND_OP_DATA_OUT_INSTR:
start = nand_subop_get_data_start_off(subop, i);
remaining = nand_subop_get_data_len(subop, i);
cnt = min_t(u32, remaining, NFC_SRAM_SIZE);
cmd |= NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
if (instr->type == NAND_OP_DATA_OUT_INSTR) {
cmd |= NFC_ACCESS_DIR;
memcpy_toio(nfc->regs + NFC_RAM0_BASE,
instr->ctx.data.buf.out + start,
cnt);
} else {
inbuf = instr->ctx.data.buf.in + start;
}
break ;
case NAND_OP_WAITRDY_INSTR:
cmd |= NFC_WAIT_FLAG;
break ;
}
}
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
return ret;
if (cmd & NFC_SEND_ADR) {
writel(addrs[0 ], nfc->regs + NFC_REG_ADDR_LOW);
writel(addrs[1 ], nfc->regs + NFC_REG_ADDR_HIGH);
}
if (cmd & NFC_SEND_CMD2)
writel(extcmd,
nfc->regs +
(cmd & NFC_ACCESS_DIR ?
NFC_REG_WCMD_SET : NFC_REG_RCMD_SET));
if (cmd & NFC_DATA_TRANS)
writel(cnt, nfc->regs + NFC_REG_CNT);
writel(cmd, nfc->regs + NFC_REG_CMD);
ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG,
!(cmd & NFC_WAIT_FLAG) && cnt < 64 ,
0 );
if (ret)
return ret;
if (inbuf)
memcpy_fromio(inbuf, nfc->regs + NFC_RAM0_BASE, cnt);
return 0 ;
}
static int sunxi_nfc_soft_waitrdy(struct nand_chip *nand,
const struct nand_subop *subop)
{
return nand_soft_waitrdy(nand,
subop->instrs[0 ].ctx.waitrdy.timeout_ms);
}
static const struct nand_op_parser sunxi_nfc_op_parser = NAND_OP_PARSER(
NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop,
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_ADDR_ELEM(true , 8 ),
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_WAITRDY_ELEM(true ),
NAND_OP_PARSER_PAT_DATA_IN_ELEM(true , 1024 )),
NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop,
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_ADDR_ELEM(true , 8 ),
NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true , 1024 ),
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_WAITRDY_ELEM(true )),
);
static const struct nand_op_parser sunxi_nfc_norb_op_parser = NAND_OP_PARSER(
NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop,
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_ADDR_ELEM(true , 8 ),
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_DATA_IN_ELEM(true , 1024 )),
NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop,
NAND_OP_PARSER_PAT_CMD_ELEM(true ),
NAND_OP_PARSER_PAT_ADDR_ELEM(true , 8 ),
NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true , 1024 ),
NAND_OP_PARSER_PAT_CMD_ELEM(true )),
NAND_OP_PARSER_PATTERN(sunxi_nfc_soft_waitrdy,
NAND_OP_PARSER_PAT_WAITRDY_ELEM(false )),
);
static int sunxi_nfc_exec_op(struct nand_chip *nand,
const struct nand_operation *op, bool check_only)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
const struct nand_op_parser *parser;
if (!check_only)
sunxi_nfc_select_chip(nand, op->cs);
if (sunxi_nand->sels[op->cs].rb >= 0 )
parser = &sunxi_nfc_op_parser;
else
parser = &sunxi_nfc_norb_op_parser;
return nand_op_parser_exec_op(nand, parser, op, check_only);
}
static const struct nand_controller_ops sunxi_nand_controller_ops = {
.attach_chip = sunxi_nand_attach_chip,
.setup_interface = sunxi_nfc_setup_interface,
.exec_op = sunxi_nfc_exec_op,
};
static void sunxi_nand_chips_cleanup(struct sunxi_nfc *nfc)
{
struct sunxi_nand_chip *sunxi_nand;
struct nand_chip *chip;
int ret;
while (!list_empty(&nfc->chips)) {
sunxi_nand = list_first_entry(&nfc->chips,
struct sunxi_nand_chip,
node);
chip = &sunxi_nand->nand;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
nand_cleanup(chip);
list_del(&sunxi_nand->node);
}
}
static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
struct device_node *np)
{
struct sunxi_nand_chip *sunxi_nand;
struct mtd_info *mtd;
struct nand_chip *nand;
int nsels;
int ret;
int i;
u32 tmp;
if (!of_get_property(np, "reg" , &nsels))
return -EINVAL;
nsels /= sizeof (u32);
if (!nsels) {
dev_err(dev, "invalid reg property size\n" );
return -EINVAL;
}
sunxi_nand = devm_kzalloc(dev, struct_size(sunxi_nand, sels, nsels),
GFP_KERNEL);
if (!sunxi_nand)
return -ENOMEM;
sunxi_nand->nsels = nsels;
for (i = 0 ; i < nsels; i++) {
ret = of_property_read_u32_index(np, "reg" , i, &tmp);
if (ret) {
dev_err(dev, "could not retrieve reg property: %d\n" ,
ret);
return ret;
}
if (tmp > NFC_MAX_CS) {
dev_err(dev,
"invalid reg value: %u (max CS = 7)\n" ,
tmp);
return -EINVAL;
}
if (test_and_set_bit(tmp, &nfc->assigned_cs)) {
dev_err(dev, "CS %d already assigned\n" , tmp);
return -EINVAL;
}
sunxi_nand->sels[i].cs = tmp;
if (!of_property_read_u32_index(np, "allwinner,rb" , i, &tmp) &&
tmp < 2 )
sunxi_nand->sels[i].rb = tmp;
else
sunxi_nand->sels[i].rb = -1 ;
}
nand = &sunxi_nand->nand;
/* Default tR value specified in the ONFI spec (chapter 4.15.1) */
nand->controller = &nfc->controller;
nand->controller->ops = &sunxi_nand_controller_ops;
/*
* Set the ECC mode to the default value in case nothing is specified
* in the DT .
*/
nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
nand_set_flash_node(nand, np);
mtd = nand_to_mtd(nand);
mtd->dev.parent = dev;
ret = nand_scan(nand, nsels);
if (ret)
return ret;
ret = mtd_device_register(mtd, NULL, 0 );
if (ret) {
dev_err(dev, "failed to register mtd device: %d\n" , ret);
nand_cleanup(nand);
return ret;
}
list_add_tail(&sunxi_nand->node, &nfc->chips);
return 0 ;
}
static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
{
struct device_node *np = dev->of_node;
int ret;
for_each_child_of_node_scoped(np, nand_np) {
ret = sunxi_nand_chip_init(dev, nfc, nand_np);
if (ret) {
sunxi_nand_chips_cleanup(nfc);
return ret;
}
}
return 0 ;
}
static int sunxi_nfc_dma_init(struct sunxi_nfc *nfc, struct resource *r)
{
int ret;
if (nfc->caps->has_mdma)
return 0 ;
nfc->dmac = dma_request_chan(nfc->dev, "rxtx" );
if (IS_ERR(nfc->dmac)) {
ret = PTR_ERR(nfc->dmac);
if (ret == -EPROBE_DEFER)
return ret;
/* Ignore errors to fall back to PIO mode */
dev_warn(nfc->dev, "failed to request rxtx DMA channel: %d\n" , ret);
nfc->dmac = NULL;
} else {
struct dma_slave_config dmac_cfg = { };
dmac_cfg.src_addr = r->start + nfc->caps->reg_io_data;
dmac_cfg.dst_addr = dmac_cfg.src_addr;
dmac_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmac_cfg.dst_addr_width = dmac_cfg.src_addr_width;
dmac_cfg.src_maxburst = nfc->caps->dma_maxburst;
dmac_cfg.dst_maxburst = nfc->caps->dma_maxburst;
dmaengine_slave_config(nfc->dmac, &dmac_cfg);
}
return 0 ;
}
static int sunxi_nfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *r;
struct sunxi_nfc *nfc;
int irq;
int ret;
nfc = devm_kzalloc(dev, sizeof (*nfc), GFP_KERNEL);
if (!nfc)
return -ENOMEM;
nfc->dev = dev;
nand_controller_init(&nfc->controller);
INIT_LIST_HEAD(&nfc->chips);
nfc->regs = devm_platform_get_and_ioremap_resource(pdev, 0 , &r);
if (IS_ERR(nfc->regs))
return PTR_ERR(nfc->regs);
irq = platform_get_irq(pdev, 0 );
if (irq < 0 )
return irq;
nfc->ahb_clk = devm_clk_get_enabled(dev, "ahb" );
if (IS_ERR(nfc->ahb_clk)) {
dev_err(dev, "failed to retrieve ahb clk\n" );
return PTR_ERR(nfc->ahb_clk);
}
nfc->mod_clk = devm_clk_get_enabled(dev, "mod" );
if (IS_ERR(nfc->mod_clk)) {
dev_err(dev, "failed to retrieve mod clk\n" );
return PTR_ERR(nfc->mod_clk);
}
nfc->reset = devm_reset_control_get_optional_exclusive(dev, "ahb" );
if (IS_ERR(nfc->reset))
return PTR_ERR(nfc->reset);
ret = reset_control_deassert(nfc->reset);
if (ret) {
dev_err(dev, "reset err %d\n" , ret);
return ret;
}
nfc->caps = of_device_get_match_data(&pdev->dev);
if (!nfc->caps) {
ret = -EINVAL;
goto out_ahb_reset_reassert;
}
ret = sunxi_nfc_rst(nfc);
if (ret)
goto out_ahb_reset_reassert;
writel(0 , nfc->regs + NFC_REG_INT);
ret = devm_request_irq(dev, irq, sunxi_nfc_interrupt,
0 , "sunxi-nand" , nfc);
if (ret)
goto out_ahb_reset_reassert;
ret = sunxi_nfc_dma_init(nfc, r);
if (ret)
goto out_ahb_reset_reassert;
platform_set_drvdata(pdev, nfc);
ret = sunxi_nand_chips_init(dev, nfc);
if (ret) {
dev_err(dev, "failed to init nand chips\n" );
goto out_release_dmac;
}
return 0 ;
out_release_dmac:
if (nfc->dmac)
dma_release_channel(nfc->dmac);
out_ahb_reset_reassert:
reset_control_assert(nfc->reset);
return ret;
}
static void sunxi_nfc_remove(struct platform_device *pdev)
{
struct sunxi_nfc *nfc = platform_get_drvdata(pdev);
sunxi_nand_chips_cleanup(nfc);
reset_control_assert(nfc->reset);
if (nfc->dmac)
dma_release_channel(nfc->dmac);
}
static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
.reg_io_data = NFC_REG_A10_IO_DATA,
.dma_maxburst = 4 ,
};
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
.has_mdma = true ,
.reg_io_data = NFC_REG_A23_IO_DATA,
.dma_maxburst = 8 ,
};
static const struct of_device_id sunxi_nfc_ids[] = {
{
.compatible = "allwinner,sun4i-a10-nand" ,
.data = &sunxi_nfc_a10_caps,
},
{
.compatible = "allwinner,sun8i-a23-nand-controller" ,
.data = &sunxi_nfc_a23_caps,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
static struct platform_driver sunxi_nfc_driver = {
.driver = {
.name = "sunxi_nand" ,
.of_match_table = sunxi_nfc_ids,
},
.probe = sunxi_nfc_probe,
.remove = sunxi_nfc_remove,
};
module_platform_driver(sunxi_nfc_driver);
MODULE_LICENSE("GPL" );
MODULE_AUTHOR("Boris BREZILLON" );
MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver" );
MODULE_ALIAS("platform:sunxi_nand" );
Messung V0.5 in Prozent C=97 H=95 G=95
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-10-01)
¤
*© Formatika GbR, Deutschland