--- a/arch/mips/bcm47xx/Kconfig +++ b/arch/mips/bcm47xx/Kconfig @@ -25,6 +25,7 @@ config BCM47XX_BCMA select BCMA_HOST_PCI if PCI select BCMA_DRIVER_PCI_HOSTMODE if PCI select BCMA_SFLASH + select BCMA_NFLASH default y help Add support for new Broadcom BCM47xx boards with Broadcom specific Advanced Microcontroller Bus. --- a/arch/mips/bcm47xx/bus.c +++ b/arch/mips/bcm47xx/bus.c @@ -2,6 +2,7 @@ * BCM947xx nvram variable access * * Copyright (C) 2011 Hauke Mehrtens + * Copyright (C) 2011-2012 Tathagata Das * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -46,6 +47,12 @@ void bcm47xx_sflash_struct_bcma_init(str sflash->numblocks = bcc->sflash.numblocks; sflash->size = bcc->sflash.size; } + +void bcm47xx_nflash_struct_bcma_init(struct bcm47xx_nflash *nflash, struct bcma_drv_cc *bcc) +{ + nflash->nflash_type = BCM47XX_BUS_TYPE_BCMA; + nflash->bcc = bcc; +} #endif #ifdef CONFIG_BCM47XX_SSB --- a/arch/mips/bcm47xx/nvram.c +++ b/arch/mips/bcm47xx/nvram.c @@ -4,6 +4,7 @@ * Copyright (C) 2005 Broadcom Corporation * Copyright (C) 2006 Felix Fietkau * Copyright (C) 2010-2011 Hauke Mehrtens + * Copyright (C) 2011-2012 Tathagata Das * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -21,6 +22,7 @@ #include #include #include +#include static char nvram_buf[NVRAM_SPACE]; @@ -160,6 +162,51 @@ static void early_nvram_init_ssb(void) #endif #ifdef CONFIG_BCM47XX_BCMA +static int early_nvram_init_nflash(void) +{ + struct nvram_header *header; + u32 off; + int ret; + int len; + u32 flash_size = bcm47xx_nflash.size; + u8 tmpbuf[NFL_SECTOR_SIZE]; + int i; + u32 *src, *dst; + + /* check if the struct is already initilized */ + if (!flash_size) + return -1; + + cfe_env = 0; + + off = FLASH_MIN; + while (off <= flash_size) { + ret = bcma_nflash_read(bcm47xx_nflash.bcc, off, NFL_SECTOR_SIZE, tmpbuf); + if (ret != NFL_SECTOR_SIZE) + goto done; + header = (struct nvram_header *)tmpbuf; + if (header->magic == NVRAM_HEADER) + goto found; + off <<= 1; + } + + ret = -1; + goto done; + +found: + len = header->len; + header = (struct nvram_header *) KSEG1ADDR(NAND_FLASH1 + off); + src = (u32 *) header; + dst = (u32 *) nvram_buf; + for (i = 0; i < sizeof(struct nvram_header); i += 4) + *dst++ = *src++; + for (; i < len && i < NVRAM_SPACE; i += 4) + *dst++ = *src++; + ret = 0; +done: + return ret; +} + static void early_nvram_init_bcma(void) { int err; @@ -173,6 +220,11 @@ static void early_nvram_init_bcma(void) if (err < 0) printk(KERN_WARNING "can not read from flash: %i\n", err); break; + case BCMA_NFLASH: + err = early_nvram_init_nflash(); + if (err < 0) + printk(KERN_WARNING "can not read from nflash: %i\n", err); + break; default: printk(KERN_WARNING "unknow flash type\n"); } --- a/arch/mips/bcm47xx/setup.c +++ b/arch/mips/bcm47xx/setup.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Michael Buesch * Copyright (C) 2010 Waldemar Brodkorb * Copyright (C) 2010-2012 Hauke Mehrtens + * Copyright (C) 2011-2012 Tathagata Das * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -234,6 +235,21 @@ static int bcm47xx_get_sprom_bcma(struct } } +struct bcm47xx_nflash bcm47xx_nflash; + +static struct resource bcm47xx_nflash_resource = { + .name = "bcm47xx_nflash", + .start = 0, + .end = 0, + .flags = 0, +}; + +static struct platform_device bcm47xx_nflash_dev = { + .name = "bcm47xx_nflash", + .resource = &bcm47xx_nflash_resource, + .num_resources = 1, +}; + static void __init bcm47xx_register_bcma(void) { int err; @@ -248,6 +264,9 @@ static void __init bcm47xx_register_bcma if (bcm47xx_bus.bcma.bus.drv_cc.flash_type == BCMA_SFLASH) bcm47xx_sflash_struct_bcma_init(&bcm47xx_sflash, &bcm47xx_bus.bcma.bus.drv_cc); + + if (bcm47xx_bus.bcma.bus.drv_cc.flash_type == BCMA_NFLASH) + bcm47xx_nflash_struct_bcma_init(&bcm47xx_nflash, &bcm47xx_bus.bcma.bus.drv_cc); bcm47xx_fill_bcma_boardinfo(&bcm47xx_bus.bcma.bus.boardinfo, NULL); } @@ -264,6 +283,9 @@ static int __init bcm47xx_register_flash case BCMA_SFLASH: bcm47xx_sflash_dev.dev.platform_data = &bcm47xx_sflash; return platform_device_register(&bcm47xx_sflash_dev); + case BCMA_NFLASH: + bcm47xx_nflash_dev.dev.platform_data = &bcm47xx_nflash; + return platform_device_register(&bcm47xx_nflash_dev); default: printk(KERN_ERR "No flash device found\n"); return -1; --- a/arch/mips/include/asm/mach-bcm47xx/bus.h +++ b/arch/mips/include/asm/mach-bcm47xx/bus.h @@ -2,6 +2,7 @@ * BCM947xx nvram variable access * * Copyright (C) 2011 Hauke Mehrtens + * Copyright (C) 2011-2012 Tathagata Das * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -12,6 +13,7 @@ #include #include #include +#include struct bcm47xx_sflash { enum bcm47xx_bus_type sflash_type; @@ -34,3 +36,18 @@ void bcm47xx_sflash_struct_bcma_init(str void bcm47xx_sflash_struct_ssb_init(struct bcm47xx_sflash *sflash, struct ssb_chipcommon *scc); extern struct bcm47xx_sflash bcm47xx_sflash; + +struct bcm47xx_nflash { + enum bcm47xx_bus_type nflash_type; + struct bcma_drv_cc *bcc; + + u32 size; /* Total size in bytes */ + u32 next_opcode; /* Next expected command from upper NAND layer */ + + struct mtd_info mtd; + struct nand_chip nand; +}; + +void bcm47xx_nflash_struct_bcma_init(struct bcm47xx_nflash *nflash, struct bcma_drv_cc *bcc); + +extern struct bcm47xx_nflash bcm47xx_nflash; --- a/drivers/bcma/Kconfig +++ b/drivers/bcma/Kconfig @@ -43,6 +43,11 @@ config BCMA_SFLASH depends on BCMA_DRIVER_MIPS default y +config BCMA_NFLASH + bool + depends on BCMA_DRIVER_MIPS + default y + config BCMA_DRIVER_MIPS bool "BCMA Broadcom MIPS core driver" depends on BCMA && MIPS --- a/drivers/bcma/Makefile +++ b/drivers/bcma/Makefile @@ -1,6 +1,7 @@ bcma-y += main.o scan.o core.o sprom.o bcma-y += driver_chipcommon.o driver_chipcommon_pmu.o bcma-$(CONFIG_BCMA_SFLASH) += driver_chipcommon_sflash.o +bcma-$(CONFIG_BCMA_NFLASH) += driver_chipcommon_nflash.o bcma-y += driver_pci.o bcma-$(CONFIG_BCMA_DRIVER_PCI_HOSTMODE) += driver_pci_host.o bcma-$(CONFIG_BCMA_DRIVER_MIPS) += driver_mips.o --- a/drivers/bcma/bcma_private.h +++ b/drivers/bcma/bcma_private.h @@ -56,6 +56,11 @@ u32 bcma_pmu_get_clockcpu(struct bcma_dr int bcma_sflash_init(struct bcma_drv_cc *cc); #endif /* CONFIG_BCMA_SFLASH */ +#ifdef CONFIG_BCMA_NFLASH +/* driver_chipcommon_nflash.c */ +int bcma_nflash_init(struct bcma_drv_cc *cc); +#endif /* CONFIG_BCMA_NFLASH */ + #ifdef CONFIG_BCMA_HOST_PCI /* host_pci.c */ extern int __init bcma_host_pci_init(void); --- /dev/null +++ b/drivers/bcma/driver_chipcommon_nflash.c @@ -0,0 +1,154 @@ +/* + * BCMA nand flash interface + * + * Copyright 2011, Tathagata Das + * Copyright 2010, Broadcom Corporation + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include +#include +#include +#include +#include + +#include "bcma_private.h" + +/* Issue a nand flash command */ +static inline void bcma_nflash_cmd(struct bcma_drv_cc *cc, u32 opcode) +{ + bcma_cc_write32(cc, NAND_CMD_START, opcode); + bcma_cc_read32(cc, NAND_CMD_START); +} + +/* Check offset and length */ +static int bcma_nflash_offset_is_valid(struct bcma_drv_cc *cc, u32 offset, u32 len, u32 mask) +{ + if ((offset & mask) != 0 || (len & mask) != 0) { + pr_err("%s(): Address is not aligned. offset: %x, len: %x, mask: %x\n", __func__, offset, len, mask); + return 1; + } + + if ((((offset + len) >> 20) >= cc->nflash.size) && + (((offset + len) & ((1 << 20) - 1)) != 0)) { + pr_err("%s(): Address is outside Flash memory region. offset: %x, len: %x, mask: %x\n", __func__, offset, len, mask); + return 1; + } + + return 0; +} + +/* Read len bytes starting at offset into buf. Returns number of bytes read. */ +int bcma_nflash_read(struct bcma_drv_cc *cc, u32 offset, u32 len, u8 *buf) +{ + u32 mask; + int i; + u32 *to, val, res; + + mask = NFL_SECTOR_SIZE - 1; + if (bcma_nflash_offset_is_valid(cc, offset, len, mask)) + return 0; + + to = (u32 *)buf; + res = len; + while (res > 0) { + bcma_cc_write32(cc, NAND_CMD_ADDR, offset); + bcma_nflash_cmd(cc, NCMD_PAGE_RD); + if (bcma_nflash_poll(cc) < 0) + break; + val = bcma_cc_read32(cc, NAND_INTFC_STATUS); + if ((val & NIST_CACHE_VALID) == 0) + break; + bcma_cc_write32(cc, NAND_CACHE_ADDR, 0); + for (i = 0; i < NFL_SECTOR_SIZE; i += 4, to++) { + *to = bcma_cc_read32(cc, NAND_CACHE_DATA); + } + res -= NFL_SECTOR_SIZE; + offset += NFL_SECTOR_SIZE; + } + return (len - res); +} + +#define NF_RETRIES 1000000 + +/* Poll for command completion. Returns zero when complete. */ +int bcma_nflash_poll(struct bcma_drv_cc *cc) +{ + u32 retries = NF_RETRIES; + u32 pollmask = NIST_CTRL_READY|NIST_FLASH_READY; + u32 mask; + + while (retries--) { + mask = bcma_cc_read32(cc, NAND_INTFC_STATUS) & pollmask; + if (mask == pollmask) + return 0; + cpu_relax(); + } + + if (!retries) { + pr_err("bcma_nflash_poll: not ready\n"); + return -1; + } + + return 0; +} + +/* Write len bytes starting at offset into buf. Returns success (0) or failure (!0). + * Should poll for completion. + */ +int bcma_nflash_write(struct bcma_drv_cc *cc, u32 offset, u32 len, + const u8 *buf) +{ + u32 mask; + int i; + u32 *from, res, reg; + + mask = cc->nflash.pagesize - 1; + if (bcma_nflash_offset_is_valid(cc, offset, len, mask)) + return 1; + + /* disable partial page enable */ + reg = bcma_cc_read32(cc, NAND_ACC_CONTROL); + reg &= ~NAC_PARTIAL_PAGE_EN; + bcma_cc_write32(cc, NAND_ACC_CONTROL, reg); + + from = (u32 *)buf; + res = len; + while (res > 0) { + bcma_cc_write32(cc, NAND_CACHE_ADDR, 0); + for (i = 0; i < cc->nflash.pagesize; i += 4, from++) { + if (i % 512 == 0) + bcma_cc_write32(cc, NAND_CMD_ADDR, i); + bcma_cc_write32(cc, NAND_CACHE_DATA, *from); + } + bcma_cc_write32(cc, NAND_CMD_ADDR, offset + cc->nflash.pagesize - 512); + bcma_nflash_cmd(cc, NCMD_PAGE_PROG); + if (bcma_nflash_poll(cc) < 0) + break; + res -= cc->nflash.pagesize; + offset += cc->nflash.pagesize; + } + + if (res <= 0) + return 0; + else + return (len - res); +} + +/* Erase a region. Returns success (0) or failure (-1). + * Poll for completion. + */ +int bcma_nflash_erase(struct bcma_drv_cc *cc, u32 offset) +{ + if ((offset >> 20) >= cc->nflash.size) + return -1; + if ((offset & (cc->nflash.blocksize - 1)) != 0) + return -1; + + bcma_cc_write32(cc, NAND_CMD_ADDR, offset); + bcma_nflash_cmd(cc, NCMD_BLOCK_ERASE); + if (bcma_nflash_poll(cc) < 0) + return -1; + return 0; +} --- a/drivers/bcma/driver_mips.c +++ b/drivers/bcma/driver_mips.c @@ -6,6 +6,7 @@ * Copyright 2006, 2007, Michael Buesch * Copyright 2010, Bernhard Loos * Copyright 2011, Hauke Mehrtens + * Copyright (C) 2011-2012 Tathagata Das * * Licensed under the GNU/GPL. See COPYING for details. */ @@ -182,6 +183,17 @@ static void bcma_core_mips_flash_detect( { struct bcma_bus *bus = mcore->core->bus; + if (bus->drv_cc.core->id.rev == 38 + && (bus->drv_cc.status & (1 << 4)) != 0) { +#ifdef CONFIG_BCMA_NFLASH + pr_info("found nand flash.\n"); + bus->drv_cc.flash_type = BCMA_NFLASH; +#else + pr_info("NAND flash not supported.\n"); +#endif + return; + } + switch (bus->drv_cc.capabilities & BCMA_CC_CAP_FLASHT) { case BCMA_CC_FLASHT_STSER: case BCMA_CC_FLASHT_ATSER: --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -536,4 +536,12 @@ config MTD_NAND_FSMC Enables support for NAND Flash chips on the ST Microelectronics Flexible Static Memory Controller (FSMC) +config MTD_NAND_BCM47XX + tristate "bcm47xx nand flash support" + default y + depends on BCM47XX && BCMA_NFLASH + select MTD_PARTITIONS + help + Support for bcm47xx nand flash + endif # MTD_NAND --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -49,5 +49,6 @@ obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mp obj-$(CONFIG_MTD_NAND_RICOH) += r852.o obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/ +obj-$(CONFIG_MTD_NAND_BCM47XX) += bcm47xx_nand.o nand-objs := nand_base.o nand_bbt.o --- /dev/null +++ b/drivers/mtd/nand/bcm47xx_nand.c @@ -0,0 +1,506 @@ +/* + * BCMA nand flash interface + * + * Copyright (C) 2011-2012 Tathagata Das + * Copyright 2010, Broadcom Corporation + * + * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY + * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM + * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * + */ + +#define pr_fmt(fmt) "bcm47xx_nflash: " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int bcm47xx_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip); +static int bcm47xx_erase(struct mtd_info *mtd, unsigned int addr, unsigned int len); + +/* Private Global variable */ +static u32 read_offset = 0; +static u32 write_offset; + +static int +nflash_mtd_poll(struct bcm47xx_nflash *nflash, unsigned int offset, int timeout) +{ + unsigned long now = jiffies; + int ret = 0; + + for (;;) { + if (!bcma_nflash_poll(nflash->bcc)) { + ret = 0; + break; + } + if (time_after(jiffies, now + timeout)) { + pr_err("timeout while polling\n"); + ret = -ETIMEDOUT; + break; + } + udelay(1); + } + + return ret; +} + +static int +bcm47xx_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct nand_chip *nchip = (struct nand_chip *)mtd->priv; + struct bcm47xx_nflash *nflash = (struct bcm47xx_nflash *)nchip->priv; + int bytes, ret = 0; + u32 extra = 0; + u8 *tmpbuf = NULL; + int size; + u32 offset, blocksize, mask, off; + u32 skip_bytes = 0; + int need_copy = 0; + u8 *ptr = NULL; + + /* Check address range */ + if (!len) + return 0; + if ((from + len) > mtd->size) + return -EINVAL; + offset = from; + if ((offset & (NFL_SECTOR_SIZE - 1)) != 0) { + extra = offset & (NFL_SECTOR_SIZE - 1); + offset -= extra; + len += extra; + need_copy = 1; + } + size = (len + (NFL_SECTOR_SIZE - 1)) & ~(NFL_SECTOR_SIZE - 1); + if (size != len) { + need_copy = 1; + } + if (!need_copy) { + ptr = buf; + } else { + tmpbuf = (u8 *)kmalloc(size, GFP_KERNEL); + ptr = tmpbuf; + } + + blocksize = mtd->erasesize; + mask = blocksize - 1; + *retlen = 0; + while (len > 0) { + off = offset + skip_bytes; + if ((bytes = bcma_nflash_read(nflash->bcc, off, NFL_SECTOR_SIZE, ptr)) < 0) { + ret = bytes; + goto done; + } + if (bytes > len) + bytes = len; + offset += bytes; + len -= bytes; + ptr += bytes; + *retlen += bytes; + } + +done: + if (tmpbuf) { + *retlen -= extra; + memcpy(buf, tmpbuf+extra, *retlen); + kfree(tmpbuf); + } + + return ret; +} + +static void bcm47xx_write(struct mtd_info *mtd, u32 to, const u_char *buf, u32 len) +{ + struct nand_chip *nchip = (struct nand_chip *)mtd->priv; + struct bcm47xx_nflash *nflash = (struct bcm47xx_nflash *)nchip->priv; + u32 offset, blocksize, mask, off; + int read_len; + u32 copy_len, write_len, from; + u_char *write_ptr, *block; + const u_char *ptr; + int ret, bytes; + + /* Check address range */ + if (!len) { + pr_err("Error: Attempted to write too small data\n"); + return; + } + + if (!to) + return; + + if ((to + len) > mtd->size) { + pr_err("Error: Attempted to write too large data\n"); + return; + } + + ptr = buf; + block = NULL; + offset = to; + blocksize = mtd->erasesize; + if (!(block = kmalloc(blocksize, GFP_KERNEL))) + return; + mask = blocksize - 1; + while (len) { + /* Align offset */ + from = offset & ~mask; + /* Copy existing data into holding block if necessary */ + if (((offset & (blocksize-1)) != 0) || (len < blocksize)) { + if ((ret = bcm47xx_read(mtd, from, blocksize, &read_len, block))) + goto done; + if (read_len != blocksize) { + ret = -EINVAL; + goto done; + } + } + + /* Copy input data into holding block */ + copy_len = min(len, blocksize - (offset & mask)); + memcpy(block + (offset & mask), ptr, copy_len); + off = (uint) from; + /* Erase block */ + if ((ret = bcm47xx_erase(mtd, off, blocksize)) < 0) + goto done; + /* Write holding block */ + write_ptr = block; + write_len = blocksize; + if ((bytes = bcma_nflash_write(nflash->bcc, (uint)from, (uint)write_len, (u8 *) write_ptr)) != 0) { + ret = bytes; + goto done; + } + offset += copy_len; + if (len < copy_len) + len = 0; + else + len -= copy_len; + ptr += copy_len; + } + +done: + if (block) + kfree(block); + return; +} + +static int bcm47xx_erase(struct mtd_info *mtd, unsigned int addr, unsigned int len) +{ + struct nand_chip *nchip = (struct nand_chip *)mtd->priv; + struct bcm47xx_nflash *nflash = (struct bcm47xx_nflash *)nchip->priv; + + /* Check address range */ + if (!len) + return 1; + if ((addr + len) > mtd->size) + return 1; + + if (bcma_nflash_erase(nflash->bcc, addr)) { + pr_err("ERASE: nflash erase error\n"); + return 1; + } + + if (nflash_mtd_poll(nflash, addr, 10 * HZ)) { + pr_err("ERASE: nflash_mtd_poll error\n"); + return 1; + } + + return 0; +} + +/* This functions is used by upper layer to checks if device is ready */ +static int bcm47xx_dev_ready(struct mtd_info *mtd) +{ + return 1; +} + +/* Issue a nand flash command */ +static inline void bcm47xx_nflash_cmd(struct bcma_drv_cc *cc, u32 opcode) +{ + bcma_cc_write32(cc, NAND_CMD_START, opcode); + bcma_cc_read32(cc, NAND_CMD_START); +} + +static void bcm47xx_command(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + struct nand_chip *nchip = (struct nand_chip *)mtd->priv; + struct bcm47xx_nflash *nflash = (struct bcm47xx_nflash *)nchip->priv; + u32 pagesize = 1 << nchip->page_shift; + + /* Command pre-processing step */ + switch (command) { + case NAND_CMD_RESET: + bcm47xx_nflash_cmd(nflash->bcc, NCMD_FLASH_RESET); + break; + + case NAND_CMD_STATUS: + nflash->next_opcode = NAND_CMD_STATUS; + read_offset = 0; + write_offset = 0; + break; + + case NAND_CMD_READ0: + read_offset = page_addr * pagesize; + nflash->next_opcode = 0; + break; + + case NAND_CMD_READOOB: + read_offset = page_addr * pagesize; + nflash->next_opcode = 0; + break; + + case NAND_CMD_SEQIN: + write_offset = page_addr * pagesize; + nflash->next_opcode = 0; + break; + + case NAND_CMD_PAGEPROG: + nflash->next_opcode = 0; + break; + + case NAND_CMD_READID: + read_offset = column; + bcm47xx_nflash_cmd(nflash->bcc, NCMD_ID_RD); + nflash->next_opcode = NAND_DEVID; + break; + + case NAND_CMD_ERASE1: + nflash->next_opcode = 0; + bcm47xx_erase(mtd, page_addr*pagesize, pagesize); + break; + + case NAND_CMD_ERASE2: + break; + + case NAND_CMD_RNDOUT: + if (column > mtd->writesize) + read_offset += (column - mtd->writesize); + else + read_offset += column; + break; + + default: + pr_err("COMMAND not supported %x\n", command); + nflash->next_opcode = 0; + break; + } +} + +/* This function is used by upper layer for select and + * deselect of the NAND chip. + * It is dummy function. */ +static void bcm47xx_select_chip(struct mtd_info *mtd, int chip) +{ +} + +static u_char bcm47xx_read_byte(struct mtd_info *mtd) +{ + struct nand_chip *nchip = mtd->priv; + struct bcm47xx_nflash *nflash = (struct bcm47xx_nflash *)nchip->priv; + uint8_t ret = 0; + static u32 id; + + if (nflash->next_opcode == 0) + return ret; + + if (nflash->next_opcode == NAND_CMD_STATUS) + return NAND_STATUS_WP; + + id = bcma_cc_read32(nflash->bcc, nflash->next_opcode); + + if (nflash->next_opcode == NAND_DEVID) { + ret = (id >> (8*read_offset)) & 0xff; + read_offset++; + } + + return ret; +} + +static uint16_t bcm47xx_read_word(struct mtd_info *mtd) +{ + loff_t from = read_offset; + uint16_t buf = 0; + int bytes; + + bcm47xx_read(mtd, from, sizeof(buf), &bytes, (u_char *)&buf); + return buf; +} + +/* Write data of length len to buffer buf. The data to be + * written on NAND Flash is first copied to RAMbuffer. After the Data Input + * Operation by the NFC, the data is written to NAND Flash */ +static void bcm47xx_write_buf(struct mtd_info *mtd, + const u_char *buf, int len) +{ + bcm47xx_write(mtd, write_offset, buf, len); +} + +/* Read the data buffer from the NAND Flash. To read the data from NAND + * Flash first the data output cycle is initiated by the NFC, which copies + * the data to RAMbuffer. This data of length len is then copied to buffer buf. + */ +static void bcm47xx_read_buf(struct mtd_info *mtd, u_char *buf, int len) +{ + loff_t from = read_offset; + int bytes; + + bcm47xx_read(mtd, from, len, &bytes, buf); +} + +/* Used by the upper layer to verify the data in NAND Flash + * with the data in the buf. */ +static int bcm47xx_verify_buf(struct mtd_info *mtd, + const u_char *buf, int len) +{ + return -EFAULT; +} + +static int bcm47xx_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip) +{ + struct nand_chip *nchip = mtd->priv; + struct bcm47xx_nflash *nflash = (struct bcm47xx_nflash *)nchip->priv; + int i; + uint off; + u32 pagesize = 1 << nchip->page_shift; + u32 blocksize = mtd->erasesize; + + if ((ofs >> 20) >= nflash->size) + return 1; + if ((ofs & (blocksize - 1)) != 0) + return 1; + + for (i = 0; i < 2; i++) { + off = ofs + pagesize; + bcma_cc_write32(nflash->bcc, NAND_CMD_ADDR, off); + bcm47xx_nflash_cmd(nflash->bcc, NCMD_SPARE_RD); + if (bcma_nflash_poll(nflash->bcc) < 0) + break; + if ((bcma_cc_read32(nflash->bcc, NAND_INTFC_STATUS) & NIST_SPARE_VALID) != NIST_SPARE_VALID) + return 1; + if ((bcma_cc_read32(nflash->bcc, NAND_SPARE_RD0) & 0xff) != 0xff) + return 1; + } + return 0; +} + +const char *part_probes[] = { "cmdlinepart", NULL }; +static int bcm47xx_probe(struct platform_device *pdev) +{ + struct nand_chip *nchip; + struct mtd_info *mtd; + struct bcm47xx_nflash *nflash = dev_get_platdata(&pdev->dev); + int ret = 0; + + mtd = &nflash->mtd; + nchip = &nflash->nand; + + /* Register with MTD */ + mtd->priv = nchip; + mtd->owner = THIS_MODULE; + mtd->dev.parent = &pdev->dev; + + /* 50 us command delay time */ + nchip->chip_delay = 50; + + nchip->priv = nflash; + nchip->dev_ready = bcm47xx_dev_ready; + nchip->cmdfunc = bcm47xx_command; + nchip->select_chip = bcm47xx_select_chip; + nchip->read_byte = bcm47xx_read_byte; + nchip->read_word = bcm47xx_read_word; + nchip->write_buf = bcm47xx_write_buf; + nchip->read_buf = bcm47xx_read_buf; + nchip->verify_buf = bcm47xx_verify_buf; + nchip->block_bad = bcm47xx_block_bad; + nchip->options = NAND_SKIP_BBTSCAN; + + /* Not known */ + nchip->ecc.mode = NAND_ECC_NONE; + + /* first scan to find the device and get the page size */ + if (nand_scan_ident(mtd, 1, NULL)) { + pr_err("nand_scan_ident failed\n"); + ret = -ENXIO; + goto done; + } + nflash->bcc->nflash.size = mtd->size; + nflash->bcc->nflash.pagesize = 1 << nchip->page_shift; + nflash->bcc->nflash.blocksize = mtd->erasesize; + bcm47xx_nflash.size = mtd->size; + + /* second phase scan */ + if (nand_scan_tail(mtd)) { + pr_err("nand_scan_tail failed\n"); + ret = -ENXIO; + goto done; + } + + mtd->name = "bcm47xx-nflash"; + mtd->flags |= MTD_WRITEABLE; + ret = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0); + + if (ret) { + pr_err("mtd_device_register failed\n"); + return ret; + } + + return 0; + +done: + return ret; +} + +static int __devexit bcm47xx_remove(struct platform_device *pdev) +{ + struct bcm47xx_nflash *nflash = dev_get_platdata(&pdev->dev); + struct mtd_info *mtd = &nflash->mtd; + + if (nflash) { + /* Release resources, unregister device */ + nand_release(mtd); + } + + return 0; +} + +static struct platform_driver bcm47xx_driver = { + .remove = __devexit_p(bcm47xx_remove), + .driver = { + .name = "bcm47xx_nflash", + .owner = THIS_MODULE, + }, +}; + +static int __init init_bcm47xx_nflash(void) +{ + int ret = platform_driver_probe(&bcm47xx_driver, bcm47xx_probe); + + if (ret) + pr_err("error registering platform driver: %i\n", ret); + return ret; +} + +static void __exit exit_bcm47xx_nflash(void) +{ + platform_driver_unregister(&bcm47xx_driver); +} + +module_init(init_bcm47xx_nflash); +module_exit(exit_bcm47xx_nflash); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("BCM47XX NAND flash driver"); --- a/include/linux/bcma/bcma_driver_chipcommon.h +++ b/include/linux/bcma/bcma_driver_chipcommon.h @@ -436,6 +436,7 @@ struct bcma_chipcommon_pmu { enum bcma_flash_type { BCMA_PFLASH, BCMA_SFLASH, + BCMA_NFLASH, }; struct bcma_pflash { @@ -452,6 +453,14 @@ struct bcma_sflash { }; #endif /* CONFIG_BCMA_SFLASH */ +#ifdef CONFIG_BCMA_NFLASH +struct bcma_nflash { + u32 blocksize; /* Block size */ + u32 pagesize; /* Page size */ + u32 size; /* Total size in bytes */ +}; +#endif + struct bcma_serial_port { void *regs; unsigned long clockspeed; @@ -477,6 +486,9 @@ struct bcma_drv_cc { #ifdef CONFIG_BCMA_SFLASH struct bcma_sflash sflash; #endif /* CONFIG_BCMA_SFLASH */ +#ifdef CONFIG_BCMA_NFLASH + struct bcma_nflash nflash; +#endif }; int nr_serial_ports; @@ -542,4 +554,13 @@ int bcma_sflash_write(struct bcma_drv_cc int bcma_sflash_erase(struct bcma_drv_cc *cc, u32 offset); #endif /* CONFIG_BCMA_SFLASH */ +#ifdef CONFIG_BCMA_NFLASH +/* Chipcommon nflash support. */ +int bcma_nflash_read(struct bcma_drv_cc *cc, u32 offset, u32 len, u8 *buf); +int bcma_nflash_poll(struct bcma_drv_cc *cc); +int bcma_nflash_write(struct bcma_drv_cc *cc, u32 offset, u32 len, const u8 *buf); +int bcma_nflash_erase(struct bcma_drv_cc *cc, u32 offset); +int bcma_nflash_commit(struct bcma_drv_cc *cc, u32 offset, u32 len, const u8 *buf); +#endif + #endif /* LINUX_BCMA_DRIVER_CC_H_ */ --- /dev/null +++ b/include/linux/mtd/bcm47xx_nand.h @@ -0,0 +1,134 @@ +/* + * Broadcom chipcommon NAND flash interface + * + * Copyright (C) 2011-2012 Tathagata Das + * Copyright (C) 2009, Broadcom Corporation + * All Rights Reserved. + * + * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY + * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM + * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * + */ + +#ifndef _nflash_h_ +#define _nflash_h_ + +#define NAND_FLASH1 0x1fc00000 /* MIPS Flash Region 1 */ + +/* nand_cmd_start commands */ +#define NCMD_NULL 0 +#define NCMD_PAGE_RD 1 +#define NCMD_SPARE_RD 2 +#define NCMD_STATUS_RD 3 +#define NCMD_PAGE_PROG 4 +#define NCMD_SPARE_PROG 5 +#define NCMD_COPY_BACK 6 +#define NCMD_ID_RD 7 +#define NCMD_BLOCK_ERASE 8 +#define NCMD_FLASH_RESET 9 +#define NCMD_LOCK 0xa +#define NCMD_LOCK_DOWN 0xb +#define NCMD_UNLOCK 0xc +#define NCMD_LOCK_STATUS 0xd + +/* nand_acc_control */ +#define NAC_RD_ECC_EN 0x80000000 +#define NAC_WR_ECC_EN 0x40000000 +#define NAC_RD_ECC_BLK0_EN 0x20000000 +#define NAC_FAST_PGM_RDIN 0x10000000 +#define NAC_RD_ERASED_ECC_EN 0x08000000 +#define NAC_PARTIAL_PAGE_EN 0x04000000 +#define NAC_PAGE_HIT_EN 0x01000000 +#define NAC_ECC_LEVEL0 0x00f00000 +#define NAC_ECC_LEVEL 0x000f0000 +#define NAC_SPARE_SIZE0 0x00003f00 +#define NAC_SPARE_SIZE 0x0000003f + +/* nand_config */ +#define NCF_CONFIG_LOCK 0x80000000 +#define NCF_BLOCK_SIZE_MASK 0x70000000 +#define NCF_BLOCK_SIZE_SHIFT 28 +#define NCF_DEVICE_SIZE_MASK 0x0f000000 +#define NCF_DEVICE_SIZE_SHIFT 24 +#define NCF_DEVICE_WIDTH 0x00800000 +#define NCF_PAGE_SIZE_MASK 0x00300000 +#define NCF_PAGE_SIZE_SHIFT 20 +#define NCF_FULL_ADDR_BYTES_MASK 0x00070000 +#define NCF_FULL_ADDR_BYTES_SHIFT 16 +#define NCF_COL_ADDR_BYTES_MASK 0x00007000 +#define NCF_COL_ADDR_BYTES_SHIFT 12 +#define NCF_BLK_ADDR_BYTES_MASK 0x00000700 +#define NCF_BLK_ADDR_BYTES_SHIFT 8 + +/* nand_intfc_status */ +#define NIST_CTRL_READY 0x80000000 +#define NIST_FLASH_READY 0x40000000 +#define NIST_CACHE_VALID 0x20000000 +#define NIST_SPARE_VALID 0x10000000 +#define NIST_ERASED 0x08000000 +#define NIST_STATUS 0x000000ff + +#define NFL_SECTOR_SIZE 512 + +#define NFL_TABLE_END 0xffffffff +#define NFL_BOOT_SIZE 0x200000 +#define NFL_BOOT_OS_SIZE 0x2000000 + +/* Nand flash MLC controller registers (corerev >= 38) */ +#define NAND_REVISION 0xC00 +#define NAND_CMD_START 0xC04 +#define NAND_CMD_ADDR_X 0xC08 +#define NAND_CMD_ADDR 0xC0C +#define NAND_CMD_END_ADDR 0xC10 +#define NAND_CS_NAND_SELECT 0xC14 +#define NAND_CS_NAND_XOR 0xC18 +#define NAND_SPARE_RD0 0xC20 +#define NAND_SPARE_RD4 0xC24 +#define NAND_SPARE_RD8 0xC28 +#define NAND_SPARE_RD12 0xC2C +#define NAND_SPARE_WR0 0xC30 +#define NAND_SPARE_WR4 0xC34 +#define NAND_SPARE_WR8 0xC38 +#define NAND_SPARE_WR12 0xC3C +#define NAND_ACC_CONTROL 0xC40 +#define NAND_CONFIG 0xC48 +#define NAND_TIMING_1 0xC50 +#define NAND_TIMING_2 0xC54 +#define NAND_SEMAPHORE 0xC58 +#define NAND_DEVID 0xC60 +#define NAND_DEVID_X 0xC64 +#define NAND_BLOCK_LOCK_STATUS 0xC68 +#define NAND_INTFC_STATUS 0xC6C +#define NAND_ECC_CORR_ADDR_X 0xC70 +#define NAND_ECC_CORR_ADDR 0xC74 +#define NAND_ECC_UNC_ADDR_X 0xC78 +#define NAND_ECC_UNC_ADDR 0xC7C +#define NAND_READ_ERROR_COUNT 0xC80 +#define NAND_CORR_STAT_THRESHOLD 0xC84 +#define NAND_READ_ADDR_X 0xC90 +#define NAND_READ_ADDR 0xC94 +#define NAND_PAGE_PROGRAM_ADDR_X 0xC98 +#define NAND_PAGE_PROGRAM_ADDR 0xC9C +#define NAND_COPY_BACK_ADDR_X 0xCA0 +#define NAND_COPY_BACK_ADDR 0xCA4 +#define NAND_BLOCK_ERASE_ADDR_X 0xCA8 +#define NAND_BLOCK_ERASE_ADDR 0xCAC +#define NAND_INV_READ_ADDR_X 0xCB0 +#define NAND_INV_READ_ADDR 0xCB4 +#define NAND_BLK_WR_PROTECT 0xCC0 +#define NAND_ACC_CONTROL_CS1 0xCD0 +#define NAND_CONFIG_CS1 0xCD4 +#define NAND_TIMING_1_CS1 0xCD8 +#define NAND_TIMING_2_CS1 0xCDC +#define NAND_SPARE_RD16 0xD30 +#define NAND_SPARE_RD20 0xD34 +#define NAND_SPARE_RD24 0xD38 +#define NAND_SPARE_RD28 0xD3C +#define NAND_CACHE_ADDR 0xD40 +#define NAND_CACHE_DATA 0xD44 +#define NAND_CTRL_CONFIG 0xD48 +#define NAND_CTRL_STATUS 0xD4C + +#endif /* _nflash_h_ */