[PATCH] basic pxa3 nand controller support
Vitaly Wool
vitalywool at gmail.com
Sat Jan 26 08:44:47 EST 2008
FOn Jan 25, 2008 8:12 AM, Sergey Podstavin <spodstavin at ru.mvista.com> wrote:
> This is updated pxa3 nand controller support, io mapping was removed,
> partition tables were moved outside of the driver into platform code,
> some cleanup was done.
<snip>
> @@ -86,9 +86,9 @@ ENTRY(stext)
> bl __lookup_processor_type @ r5=procinfo r9=cpuid
> movs r10, r5 @ invalid processor (r5=0)?
> beq __error_p @ yes, error 'p'
> - bl __lookup_machine_type @ r5=machinfo
> - movs r8, r5 @ invalid machine (r5=0)?
> - beq __error_a @ yes, error 'a'
> +// bl __lookup_machine_type @ r5=machinfo
> +// movs r8, r5 @ invalid machine (r5=0)?
> +// beq __error_a @ yes, error 'a'
> bl __vet_atags
> bl __create_page_tables
Nice cleanup :P
> +#include <linux/mtd/nand.h>
I wonder if you really need this header.
> +void __init set_pxa_nand_info(struct pxa3xx_nand_platform_data *info)
> +{
> + nand_device.dev.platform_data = info;
> +}
That doesn't belong to zylonite.c.
> diff --git a/drivers/mtd/nand/pxa3xx_nand.c b/drivers/mtd/nand/pxa3xx_nand.c
> new file mode 100644
> index 0000000..11bebbf
> --- /dev/null
> +++ b/drivers/mtd/nand/pxa3xx_nand.c
> @@ -0,0 +1,1385 @@
> +/*
> + * drivers/mtd/nand/pxa3xx_nand.c
> + *
> + * Copyright (C) 2005 Intel Corporation
> + * Copyright (C) 2006 Marvell International Ltd.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/delay.h>
> +#include <linux/clk.h>
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +#include <asm/io.h>
> +#include <asm/irq.h>
> +#include <asm/dma.h>
> +
> +#include <asm/arch/pxa-regs.h>
> +#include <asm/arch/pxa3xx_nand.h>
> +
> +#define CHIP_DELAY_TIMEOUT (2 * HZ/10)
> +
> +/* registers and bit definitions */
> +#define NDCR (0x00) /* Control register */
> +#define NDTR0CS0 (0x04) /* Timing Parameter 0 for CS0 */
> +#define NDTR1CS0 (0x0C) /* Timing Parameter 1 for CS0 */
> +#define NDSR (0x14) /* Status Register */
> +#define NDPCR (0x18) /* Page Count Register */
> +#define NDBDR0 (0x1C) /* Bad Block Register 0 */
> +#define NDBDR1 (0x20) /* Bad Block Register 1 */
> +#define NDDB (0x40) /* Data Buffer */
> +#define NDCB0 (0x48) /* Command Buffer0 */
> +#define NDCB1 (0x4C) /* Command Buffer1 */
> +#define NDCB2 (0x50) /* Command Buffer2 */
> +
> +#define NDCR_SPARE_EN (0x1 << 31)
> +#define NDCR_ECC_EN (0x1 << 30)
> +#define NDCR_DMA_EN (0x1 << 29)
> +#define NDCR_ND_RUN (0x1 << 28)
> +#define NDCR_DWIDTH_C (0x1 << 27)
> +#define NDCR_DWIDTH_M (0x1 << 26)
> +#define NDCR_PAGE_SZ (0x1 << 24)
> +#define NDCR_NCSX (0x1 << 23)
> +#define NDCR_ND_MODE (0x3 << 21)
> +#define NDCR_NAND_MODE (0x0)
> +#define NDCR_CLR_PG_CNT (0x1 << 20)
> +#define NDCR_CLR_ECC (0x1 << 19)
> +#define NDCR_RD_ID_CNT_MASK (0x7 << 16)
> +#define NDCR_RD_ID_CNT(x) (((x) << 16) & NDCR_RD_ID_CNT_MASK)
> +
> +#define NDCR_RA_START (0x1 << 15)
> +#define NDCR_PG_PER_BLK (0x1 << 14)
> +#define NDCR_ND_ARB_EN (0x1 << 12)
> +
> +#define NDSR_MASK (0xfff)
> +#define NDSR_RDY (0x1 << 11)
> +#define NDSR_CS0_PAGED (0x1 << 10)
> +#define NDSR_CS1_PAGED (0x1 << 9)
> +#define NDSR_CS0_CMDD (0x1 << 8)
> +#define NDSR_CS1_CMDD (0x1 << 7)
> +#define NDSR_CS0_BBD (0x1 << 6)
> +#define NDSR_CS1_BBD (0x1 << 5)
> +#define NDSR_DBERR (0x1 << 4)
> +#define NDSR_SBERR (0x1 << 3)
> +#define NDSR_WRDREQ (0x1 << 2)
> +#define NDSR_RDDREQ (0x1 << 1)
> +#define NDSR_WRCMDREQ (0x1)
> +
> +#define NDCB0_AUTO_RS (0x1 << 25)
> +#define NDCB0_CSEL (0x1 << 24)
> +#define NDCB0_CMD_TYPE_MASK (0x7 << 21)
> +#define NDCB0_CMD_TYPE(x) (((x) << 21) & NDCB0_CMD_TYPE_MASK)
> +#define NDCB0_NC (0x1 << 20)
> +#define NDCB0_DBC (0x1 << 19)
> +#define NDCB0_ADDR_CYC_MASK (0x7 << 16)
> +#define NDCB0_ADDR_CYC(x) (((x) << 16) & NDCB0_ADDR_CYC_MASK)
> +#define NDCB0_CMD2_MASK (0xff << 8)
> +#define NDCB0_CMD1_MASK (0xff)
> +#define NDCB0_ADDR_CYC_SHIFT (16)
> +
> +/* dma-able I/O address for the NAND data and commands */
> +#define NDCB0_DMA_ADDR (0x43100048)
> +#define NDDB_DMA_ADDR (0x43100040)
> +
> +/* macros for registers read/write */
> +#define nand_writel(info, off, val) \
> + __raw_writel((val), (info)->mmio_base + (off))
> +
> +#define nand_readl(info, off) \
> + __raw_readl((info)->mmio_base + (off))
> +
> +/* error code and state */
> +enum {
> + ERR_NONE = 0,
> + ERR_DMABUSERR = -1,
> + ERR_SENDCMD = -2,
> + ERR_DBERR = -3,
> + ERR_BBERR = -4,
> + ERR_BUSY = -5,
> +};
> +
> +enum {
> + STATE_CMD_SEND = 1,
> + STATE_CMD_HANDLE,
> + STATE_DMA_TRANSFER,
> + STATE_DMA_DONE,
> + STATE_READY,
> + STATE_SUSPENDED,
> + STATE_DATA_TRANSFER,
> +};
> +
> +struct pxa3xx_nand_timing {
> + unsigned int tCH; /* Enable signal hold time */
> + unsigned int tCS; /* Enable signal setup time */
> + unsigned int tWH; /* ND_nWE high duration */
> + unsigned int tWP; /* ND_nWE pulse time */
> + unsigned int tRH; /* ND_nRE high duration */
> + unsigned int tRP; /* ND_nRE pulse width */
> + unsigned int tR; /* ND_nWE high to ND_nRE low for read */
> + unsigned int tWHR; /* ND_nWE high to ND_nRE low for status read */
> + unsigned int tAR; /* ND_ALE low to ND_nRE low delay */
> +};
> +
> +struct pxa3xx_nand_cmdset {
> + uint16_t read1;
> + uint16_t read2;
> + uint16_t program;
> + uint16_t read_status;
> + uint16_t read_id;
> + uint16_t erase;
> + uint16_t reset;
> + uint16_t lock;
> + uint16_t unlock;
> + uint16_t lock_status;
> +};
> +
> +struct pxa3xx_nand_flash {
> + struct pxa3xx_nand_timing *timing; /* NAND Flash timing */
> + struct pxa3xx_nand_cmdset *cmdset;
> +
> + uint32_t page_per_block;/* Pages per block (PG_PER_BLK) */
> + uint32_t page_size; /* Page size in bytes (PAGE_SZ) */
> + uint32_t flash_width; /* Width of Flash memory (DWIDTH_M) */
> + uint32_t dfc_width; /* Width of flash controller(DWIDTH_C) */
> + uint32_t num_blocks; /* Number of physical blocks in Flash */
> + uint32_t chip_id;
> +
> + /* NOTE: these are automatically calculated, do not define */
> + size_t oob_size;
> + size_t read_id_bytes;
> +
> + unsigned int col_addr_cycles;
> + unsigned int row_addr_cycles;
> +};
> +
> +struct pxa3xx_nand_info {
> + struct nand_chip nand_chip;
> +
> + struct platform_device *pdev;
> + struct pxa3xx_nand_flash *flash_info;
> +
> + struct clk *clk;
> + void __iomem *mmio_base;
> +
> + unsigned int buf_start;
> + unsigned int buf_count;
> +
> + /* DMA information */
> + unsigned char *data_buff;
> + dma_addr_t data_buff_phys;
> + size_t data_buff_size;
> + int data_dma_ch;
> + struct pxa_dma_desc *data_desc;
> + dma_addr_t data_desc_addr;
> +
> + uint32_t reg_ndcr;
> +
> + /* saved column/page_addr during CMD_SEQIN */
> + int seqin_column;
> + int seqin_page_addr;
> +
> + /* relate to the command */
> + unsigned int state;
> +
> + int use_ecc; /* use HW ECC ? */
> + int use_dma; /* use DMA ? */
> +
> + size_t data_size; /* data size in FIFO */
> + unsigned int cur_cmd;
> + int retcode;
> + struct completion cmd_complete;
> +
> + /* generated NDCBx register values */
> + uint32_t ndcb0;
> + uint32_t ndcb1;
> + uint32_t ndcb2;
> +};
> +
> +static int use_dma = 0;
> +module_param(use_dma, bool, 0444);
> +MODULE_PARM_DESC(use_dma, "enable DMA for data transfering to/from NAND HW");
> +
> +static struct pxa3xx_nand_cmdset smallpage_cmdset = {
> + .read1 = 0x0000,
> + .read2 = 0x0050,
> + .program = 0x1080,
> + .read_status = 0x0070,
> + .read_id = 0x0090,
> + .erase = 0xD060,
> + .reset = 0x00FF,
> + .lock = 0x002A,
> + .unlock = 0x2423,
> + .lock_status = 0x007A,
> +};
> +
> +static struct pxa3xx_nand_cmdset largepage_cmdset = {
> + .read1 = 0x3000,
> + .read2 = 0x0050,
> + .program = 0x1080,
> + .read_status = 0x0070,
> + .read_id = 0x0090,
> + .erase = 0xD060,
> + .reset = 0x00FF,
> + .lock = 0x002A,
> + .unlock = 0x2423,
> + .lock_status = 0x007A,
> +};
> +
> +static struct pxa3xx_nand_timing samsung512MbX16_timing = {
> + .tCH = 10,
> + .tCS = 0,
> + .tWH = 20,
> + .tWP = 40,
> + .tRH = 30,
> + .tRP = 40,
> + .tR = 11123,
> + .tWHR = 110,
> + .tAR = 10,
> +};
> +
> +static struct pxa3xx_nand_flash samsung512MbX16 = {
> + .timing = &samsung512MbX16_timing,
> + .cmdset = &smallpage_cmdset,
> + .page_per_block = 32,
> + .page_size = 512,
> + .flash_width = 16,
> + .dfc_width = 16,
> + .num_blocks = 4096,
> + .chip_id = 0x46ec,
> +};
> +
> +static struct pxa3xx_nand_timing micron_timing = {
> + .tCH = 10,
> + .tCS = 25,
> + .tWH = 15,
> + .tWP = 25,
> + .tRH = 15,
> + .tRP = 25,
> + .tR = 25000,
> + .tWHR = 60,
> + .tAR = 10,
> +};
> +
> +static struct pxa3xx_nand_flash micron1GbX8 = {
> + .timing = µn_timing,
> + .cmdset = &largepage_cmdset,
> + .page_per_block = 64,
> + .page_size = 2048,
> + .flash_width = 8,
> + .dfc_width = 8,
> + .num_blocks = 1024,
> + .chip_id = 0xa12c,
> +};
> +
> +static struct pxa3xx_nand_flash micron1GbX16 = {
> + .timing = µn_timing,
> + .cmdset = &largepage_cmdset,
> + .page_per_block = 64,
> + .page_size = 2048,
> + .flash_width = 16,
> + .dfc_width = 16,
> + .num_blocks = 1024,
> + .chip_id = 0xb12c,
> +};
> +
> +static struct pxa3xx_nand_flash *builtin_flash_types[] = {
> + &samsung512MbX16,
> + µn1GbX8,
> + µn1GbX16,
> +};
> +
> +#define NDTR0_tCH(c) (min((c), 7) << 19)
> +#define NDTR0_tCS(c) (min((c), 7) << 16)
> +#define NDTR0_tWH(c) (min((c), 7) << 11)
> +#define NDTR0_tWP(c) (min((c), 7) << 8)
> +#define NDTR0_tRH(c) (min((c), 7) << 3)
> +#define NDTR0_tRP(c) (min((c), 7) << 0)
> +
> +#define NDTR1_tR(c) (min((c), 65535) << 16)
> +#define NDTR1_tWHR(c) (min((c), 15) << 4)
> +#define NDTR1_tAR(c) (min((c), 15) << 0)
> +
> +/* convert nano-seconds to nand flash controller clock cycles */
> +#define ns2cycle(ns, clk) (int)(((ns) * (clk / 1000000) / 1000) + 1)
> +
> +static void pxa3xx_nand_set_timing(struct pxa3xx_nand_info *info,
> + struct pxa3xx_nand_timing *t)
> +{
> + unsigned long nand_clk = clk_get_rate(info->clk);
> + uint32_t ndtr0, ndtr1;
> +
> + ndtr0 = NDTR0_tCH(ns2cycle(t->tCH, nand_clk)) |
> + NDTR0_tCS(ns2cycle(t->tCS, nand_clk)) |
> + NDTR0_tWH(ns2cycle(t->tWH, nand_clk)) |
> + NDTR0_tWP(ns2cycle(t->tWP, nand_clk)) |
> + NDTR0_tRH(ns2cycle(t->tRH, nand_clk)) |
> + NDTR0_tRP(ns2cycle(t->tRP, nand_clk));
> +
> + ndtr1 = NDTR1_tR(ns2cycle(t->tR, nand_clk)) |
> + NDTR1_tWHR(ns2cycle(t->tWHR, nand_clk)) |
> + NDTR1_tAR(ns2cycle(t->tAR, nand_clk));
> +
> + nand_writel(info, NDTR0CS0, ndtr0);
> + nand_writel(info, NDTR1CS0, ndtr1);
> +}
> +
> +#define WAIT_EVENT_TIMEOUT 10
> +
> +static int wait_for_event(struct pxa3xx_nand_info *info, uint32_t event)
> +{
> + int timeout = WAIT_EVENT_TIMEOUT;
> + uint32_t ndsr;
> +
> + while (timeout--) {
> + ndsr = nand_readl(info, NDSR) & NDSR_MASK;
> + if (ndsr & event) {
> + nand_writel(info, NDSR, ndsr);
> + return 0;
> + }
> + udelay(10);
> + }
> +
> + return -ETIMEDOUT;
> +}
> +
> +static int prepare_read_prog_cmd(struct pxa3xx_nand_info *info,
> + uint16_t cmd, int column, int page_addr)
> +{
> + struct pxa3xx_nand_flash *f = info->flash_info;
> + struct pxa3xx_nand_cmdset *cmdset = f->cmdset;
> +
> + /* calculate data size */
> + switch (f->page_size) {
> + case 2048:
> + info->data_size = (info->use_ecc) ? 2088 : 2112;
> + break;
> + case 512:
> + info->data_size = (info->use_ecc) ? 520 : 528;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /* generate values for NDCBx registers */
> + info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0);
> + info->ndcb0 |= NDCB0_ADDR_CYC(f->row_addr_cycles + f->col_addr_cycles);
> +
> + if (f->col_addr_cycles == 2) {
> + /* large block, 2 cycles for column address
> + * row address starts from 3rd cycle
> + */
> + info->ndcb1 |= (page_addr << 16) | (column & 0xffff);
> + if (f->row_addr_cycles == 3)
> + info->ndcb2 = (page_addr >> 16) & 0xff;
> + } else
> + /* small block, 1 cycles for column address
> + * row address starts from 2nd cycle
> + */
> + info->ndcb1 = (page_addr << 8) | (column & 0xff);
> +
> + if (cmd == cmdset->program)
> + info->ndcb0 |= NDCB0_CMD_TYPE(1) | NDCB0_AUTO_RS;
> +
> + info->cur_cmd = cmd;
> + return 0;
> +}
> +
> +static int prepare_erase_cmd(struct pxa3xx_nand_info *info,
> + uint16_t cmd, int page_addr)
> +{
> + info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0);
> + info->ndcb0 |= NDCB0_CMD_TYPE(2) | NDCB0_AUTO_RS | NDCB0_ADDR_CYC(3);
> + info->ndcb1 = page_addr;
> +
> + info->cur_cmd = cmd;
> + return 0;
> +}
> +
> +static int prepare_other_cmd(struct pxa3xx_nand_info *info, uint16_t cmd)
> +{
> + struct pxa3xx_nand_cmdset *cmdset = info->flash_info->cmdset;
> +
> + info->ndcb0 = cmd | ((cmd & 0xff00) ? NDCB0_DBC : 0);
> +
> + if (cmd == cmdset->read_id) {
> + info->ndcb0 |= NDCB0_CMD_TYPE(3);
> + info->data_size = 8;
> + } else if (cmd == cmdset->read_status) {
> + info->ndcb0 |= NDCB0_CMD_TYPE(4);
> + info->data_size = 8;
> + } else if (cmd == cmdset->reset || cmd == cmdset->lock ||
> + cmd == cmdset->unlock) {
> + info->ndcb0 |= NDCB0_CMD_TYPE(5);
> + } else
> + return -EINVAL;
> +
> + info->cur_cmd = cmd;
> + return 0;
> +}
> +
> +static void enable_int(struct pxa3xx_nand_info *info, uint32_t int_mask)
> +{
> + uint32_t ndcr;
> +
> + ndcr = nand_readl(info, NDCR);
> + nand_writel(info, NDCR, ndcr & ~int_mask);
> +}
> +
> +static void disable_int(struct pxa3xx_nand_info *info, uint32_t int_mask)
> +{
> + uint32_t ndcr;
> +
> + ndcr = nand_readl(info, NDCR);
> + nand_writel(info, NDCR, ndcr | int_mask);
> +}
> +
> +/* NOTE: it is a must to set ND_RUN firstly, then write command buffer
> + * otherwise, it does not work
> + */
> +static int write_cmd(struct pxa3xx_nand_info *info)
> +{
> + uint32_t ndcr;
> +
> + /* clear status bits and run */
> + nand_writel(info, NDSR, NDSR_MASK);
> +
> + ndcr = info->reg_ndcr;
> +
> + ndcr |= info->use_ecc ? NDCR_ECC_EN : 0;
> + ndcr |= info->use_dma ? NDCR_DMA_EN : 0;
> + ndcr |= NDCR_ND_RUN;
> +
> + nand_writel(info, NDCR, ndcr);
> +
> + if (wait_for_event(info, NDSR_WRCMDREQ)) {
> + printk(KERN_ERR "timed out writing command\n");
> + return -ETIMEDOUT;
> + }
> +
> + nand_writel(info, NDCB0, info->ndcb0);
> + nand_writel(info, NDCB0, info->ndcb1);
> + nand_writel(info, NDCB0, info->ndcb2);
> + return 0;
> +}
> +
> +/* NOTE: {read, write}_fifo_pio() assume the buffer is 4-byte aligned,
> + * and data_size are 4-byte multiples as well
> + */
Why is that? Is it a hardware requirement?
Then, you're checking data_size % 4 but you don't check buffer
alignment. What's the reason for that?
> +static int pxa3xx_nand_do_cmd(struct pxa3xx_nand_info *info, uint32_t event)
> +{
> + uint32_t ndcr;
> + int ret, timeout = CHIP_DELAY_TIMEOUT;
> +
> + info->state = STATE_CMD_SEND;
> +
> + if (write_cmd(info)) {
> + info->retcode = ERR_SENDCMD;
> + goto fail_stop;
> + }
> +
> + info->state = STATE_CMD_HANDLE;
> +
> + if (info->use_dma)
> + setup_data_dma(info);
> +
> + enable_int(info, event);
This is unclear to me. It looks like you use PIO NAND interrupt both
for DMA and PIO transfers. Can you please elaborate on that?
> +
> + ret = wait_for_completion_timeout(&info->cmd_complete, timeout);
> + if (!ret) {
> + printk(KERN_ERR "command execution timed out\n");
> + info->retcode = ERR_SENDCMD;
> + goto fail_stop;
> + }
> +
> + if (info->use_dma == 0 && info->data_size > 0)
> + if (handle_data_pio(info))
> + goto fail_stop;
> +
> + return 0;
> +
> +fail_stop:
> + ndcr = nand_readl(info, NDCR);
> + nand_writel(info, NDCR, ndcr & ~NDCR_ND_RUN);
> + udelay(10);
> + return -ETIMEDOUT;
> +}
> +
> +static int pxa3xx_nand_dev_ready(struct mtd_info *mtd)
> +{
> + struct pxa3xx_nand_info *info = mtd->priv;
> + return (nand_readl(info, NDSR) & NDSR_RDY) ? 1 : 0;
> +}
> +
> +static inline int is_buf_blank(uint8_t *buf, size_t len)
> +{
> + for (; len > 0; len--)
> + if (*buf++ != 0xff)
> + return 0;
> + return 1;
> +}
> +
> +static void pxa3xx_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
> + int column, int page_addr )
<snip>
> + default:
> + printk(KERN_ERR "non-supported command.\n");
Is random input really unsupported?
> +
> + info->data_buff = dma_alloc_coherent(&pdev->dev, MAX_BUFF_SIZE,
> + &info->data_buff_phys, GFP_KERNEL);
> + if (info->data_buff == NULL) {
> + dev_err(&pdev->dev, "failed to allocate dma buffer\n");
> + return -ENOMEM;
> + }
Does this really work with coherent_dma_mask not have been set anywhere?
Thanks,
Vitaly
More information about the linux-mtd
mailing list