/****************************************************************************** ** ** FILE NAME : ifxmips_sflash.c ** PROJECT : IFX UEIP ** MODULES : 25 types of Serial Flash ** ** DATE : 03 July 2009 ** AUTHOR : Lei Chuanhua ** DESCRIPTION : SPI Flash MTD Driver ** COPYRIGHT : Copyright (c) 2009 ** Infineon Technologies AG ** Am Campeon 1-12, 85579 Neubiberg, Germany ** ** 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 Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** HISTORY ** $Date $Author $Comment ** 03,July 2009 Lei Chuanhua Initial UEIP release *******************************************************************************/ /*! \defgroup IFX_SFLASH SSC Serial Flash module \brief ifx serial flash driver module */ /*! \defgroup IFX_SFLASH_OS OS APIs \ingroup IFX_SFLASH \brief IFX serial flash driver OS interface functions */ /*! \defgroup IFX_SFLASH_INTERNAL Internal APIs \ingroup IFX_SFLASH \brief IFX serial flash driver functions */ /*! \file ifxmips_sflash.c \ingroup IFX_SFLASH \brief ifx serial flash driver source file */ #ifndef EXPORT_SYMTAB #define EXPORT_SYMTAB #endif #include #include #include #include #include #include #include #include #include #include #include #include #include /* Project header */ #include #include #include #include #include #include "ifxmips_sflash.h" #if defined(CONFIG_TFFS_DEV_MTDNOR) || defined(CONFIG_TFFS_DEV_LEGACY) #include #include #include #include "ifxmips_ssc_reg.h" static struct mtd_info *panic_reinit(struct mtd_info *mtd); #endif #define IFX_SFLASH_VER_MAJOR 1 #define IFX_SFLASH_VER_MID 1 #define IFX_SFLASH_VER_MINOR 9 #define IFX_SFLASH_NAME "ifx_sflash" #define IFX_SFLASH_ADDR_CYCLES 3 /* 24bit address */ //#define IFX_SPI_FLASH_DBG #if defined(IFX_SPI_FLASH_DBG) #define IFX_SFLASH_PRINT(format, arg...) \ printk("%s: " format, __func__, ##arg) #define INLINE #else #define IFX_SFLASH_PRINT(format, arg...) \ pr_debug("%s: " format, __func__, ##arg) #define INLINE inline #endif extern struct mtd_partition ifx_spi_partitions[IFX_MTD_SPI_PARTS]; DEFINE_SEMAPHORE(ifx_sflash_sem); static ifx_spi_dev_t *spi_sflash = NULL; /* * NOTE: double check command sets and memory organization when you add * more flash chips. This current list focusses on newer chips, which * have been converging on command sets which including JEDEC ID. */ static struct ifx_sflash_manufacturer_info flash_manufacturers[] = { { /* Spansion -- single (large) sector size only, at least * for the chips listed here (without boot sectors). */ .name = "Spansion", .id = JED_MANU_SPANSION, .flashes = { { "S25Sl004", 0x0212, 64 * 1024, 8, 0, }, { "S25Sl008", 0x0213, 64 * 1024, 16, 0, }, { "S25LF016", 0x0214, 64 * 1024, 32, 0, }, { "S25LF032", 0x0215, 64 * 1024, 64, 0, }, { "S25LF064", 0x0216, 64 * 1024, 128, 0, }, { "", 0x0, 0, 0, 0,}, { "S25LF0128", 0x0218, 256 * 1024, 64, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, }, }, { /* ST Microelectronics -- newer production may have feature updates */ .name = "ST", .id = JED_MANU_ST, .flashes = { { "m25p05", 0x2010, 32 * 1024, 2, 0, }, { "m25p10", 0x2011, 32 * 1024, 4, 0, }, { "m25p20", 0x2012, 64 * 1024, 4, 0, }, { "m25p40", 0x2013, 64 * 1024, 8, 0, }, { "m25p16", 0x2015, 64 * 1024, 32, 0, }, { "m25p32", 0x2016, 64 * 1024, 64, 0, }, { "m25p64", 0x2017, 64 * 1024, 128, 0, }, { "m25p128", 0x2018, 256 * 1024, 64, 0, }, { "m45pe80", 0x4014, 64 * 1024, 16, 0, }, { "m45pe16", 0x4015, 64 * 1024, 32, 0, }, { "m25pe80", 0x8014, 64 * 1024, 16, 0, }, { "m25pe16", 0x8015, 64 * 1024, 32, SECT_4K, }, }, }, { /* SST -- large erase sizes are "overlays", "sectors" are 4K */ .name = "SST", .id = JED_MANU_SST, .flashes = { { "sst25vf040b", 0x258d, 64 * 1024, 8, SECT_4K, }, { "sst25vf080b", 0x258e, 64 * 1024, 16, SECT_4K, }, { "sst25vf016b", 0x2541, 64 * 1024, 32, SECT_4K, }, { "sst25vf032b", 0x254a, 64 * 1024, 64, SECT_4K, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, }, }, { .name = "Atmel", .id = JED_MANU_ATMEL, .flashes = { { "at25fs010", 0x6601, 32 * 1024, 4, SECT_4K, }, { "at25fs040", 0x6604, 64 * 1024, 8, SECT_4K, }, { "at25df041a", 0x4401, 64 * 1024, 8, SECT_4K, }, { "at25df641", 0x4800, 64 * 1024, 128, SECT_4K, }, { "at26f004", 0x0400, 64 * 1024, 8, SECT_4K, }, { "at26df081a", 0x4501, 64 * 1024, 16, SECT_4K, }, { "at26df161a", 0x4601, 64 * 1024, 32, SECT_4K, }, { "at26df321", 0x4701, 64 * 1024, 64, SECT_4K, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, }, }, { /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ .name = "Winbond", .id = JED_MANU_WINBOND, .flashes = { { "w25x10", 0x4014, 8 * 1024 * 1024, 256, SECT_4K, }, { "w25x10", 0x3011, 64 * 1024, 2, SECT_4K, }, { "w25x20", 0x3012, 64 * 1024, 4, SECT_4K, }, { "w25x40", 0x3013, 64 * 1024, 8, SECT_4K, }, { "w25x80", 0x3014, 64 * 1024, 16, SECT_4K, }, { "w25x16", 0x3015, 64 * 1024, 32, SECT_4K, }, { "w25x32", 0x3016, 64 * 1024, 64, SECT_4K, }, { "w25x64", 0x3017, 64 * 1024, 128, SECT_4K, }, { "W25P80", 0x2014, 256 * 256, 16, 0, }, { "W25P16", 0x2015, 256 * 256, 32, 0, }, { "W25P32", 0x2016, 256 * 256, 64, 0, }, { "W25P64", 0x2017, 256 * 256, 128, 0, }, }, }, { .name = "MX", .id = JED_MANU_MX, .flashes = { { "MX25P2005", 0x2012, 16 * 256, 64, 0, }, { "MX25P4005", 0x2013, 16 * 256, 128, 0, }, { "MX25P8005", 0x2014, 16 * 256, 256, SECT_4K, }, { "MX25P1605", 0x2015, 256 * 256, 32, 0, }, { "MX25P3205", 0x2016, 256 * 256, 64, 0, }, { "MX25P6405", 0x2017, 256 * 256, 128, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, { "", 0, 0, 0, 0, }, }, }, }; #ifdef CONFIG_MTD_CMDLINE_PARTS static const char *part_probes[] = { "cmdlinepart", NULL }; #endif #ifdef IFX_SPI_FLASH_DBG static void flash_dump(const char *title, const u8 *buf, size_t len) { int i, llen, lenlab = 0; const u8 *pos = buf; const int line_len = 16; printk("%s - hex_ascii(len=%lu):\n", title, (unsigned long) len); while (len) { llen = len > line_len ? line_len : len; printk("%08x: ", lenlab); for (i = 0; i < llen; i++) printk(" %02x", pos[i]); for (i = llen; i < line_len; i++) printk(" "); printk(" "); for (i = 0; i < llen; i++) { if (isprint(pos[i])) printk("%c", pos[i]); else printk("."); } for (i = llen; i < line_len; i++) printk(" "); printk("\n"); pos += llen; len -= llen; lenlab += line_len; } } #endif /* IFX_SPI_FLASH_DBG */ /*! * \fn static INLINE void u32_to_u8_addr(u32 address, u8* addr) * \brief convert address from u32 to u8 array * * \param[in] address the address to be converted, maximum 32 bit * \param[out] addr array that holds the results, maximum 4 elemets * \ingroup IFX_SFLASH_INTERNAL */ static INLINE void u32_to_u8_addr(u32 address, u8* addr) { addr[0] = (u8) ((address>>16) & 0xff); addr[1] = (u8) ((address>>8) & 0xff); addr[2] = (u8) (address & 0xff); } enum session_flags { NO_WRITE_ENABLE, WRITE_ENABLE }; static int _sflash_session(ifx_spi_dev_t *dev, u8 cmd, u8 *addr, u8 dummy_cycles, u8 *wbuf, u32 wcnt, u8 *rbuf, u32 rcnt, struct chain_queue_head **chain_queue, enum chain_queue_flags flag); /** * \fn static int ifx_sflash_session(ifx_spi_dev_t *dev, u8 cmd, u8 *addr, u8 dummy_cycles, * u8 * wbuf, u32 wcnt, u8 * rbuf, u32 rcnt) * \brief Handle serial flash read/write/erase in one central function * * \param[in] dev Pointer to ifx_spi_dev_t * \param[in] cmd Serial flash command code * \param[in] addr Flash address offset * \param[in] dummy_cycles Dummy cycles for some serial flash * \param[in] wbuf Pointer to the data packet to write. * \param[in] wcnt Amount of Bytes to write. * \param[out] rbuf Pointer to store the read data. * \param[in] rcnt Amount of Bytes to read. * \return -EINVAL Invalid read data length * \return -EBUSY Serial flash device is busy * \return 0 OK * \ingroup IFX_SFLASH_INTERNAL */ static int ifx_sflash_session(ifx_spi_dev_t *dev, u8 cmd, u8 *addr, u8 dummy_cycles, u8 *wbuf, u32 wcnt, u8 *rbuf, u32 rcnt, enum session_flags flag) { struct chain_queue_head *chain_queue = NULL; int ret; if (flag == WRITE_ENABLE) { ret = _sflash_session(dev, IFX_OPCODE_WREN, NULL, 0, NULL, 0, NULL, 0, &chain_queue, CHAIN_QUEUE_START); if (ret) { ifx_ssc_chain_finish(dev, &chain_queue); return ret; } } return _sflash_session(dev, cmd, addr, dummy_cycles, wbuf, wcnt, rbuf, rcnt, &chain_queue, CHAIN_QUEUE_FINISH); } static int _sflash_session(ifx_spi_dev_t *dev, u8 cmd, u8 *addr, u8 dummy_cycles, u8 *wbuf, u32 wcnt, u8 *rbuf, u32 rcnt, struct chain_queue_head **chain_queue, enum chain_queue_flags flag) { int i; int ret = 0; int err = 0; int start = 0; int total = 0; char *buf = dev->flash_tx_buf; /* Always use the same buffer for efficiecy */ char *tbuf; /* Sanity check */ if (unlikely(rcnt >= IFX_SFLASH_MAX_READ_SIZE)) { printk("%s: please increase read buffer size\n", __func__); return -EINVAL; } /* CMD */ buf[0] = cmd; start = 1; /* Address */ if (addr != NULL) { for (i = 0; i < dev->addr_cycles; i++) { buf[start + i] = addr[i]; } start += dev->addr_cycles; } /* Dummy cycles */ if (dummy_cycles > 0 && dummy_cycles < IFX_MAX_DUMMY_CYCLES) { for (i = 0; i < dummy_cycles; i ++) { buf[start + i] = 0; } start += dummy_cycles; } if ((wcnt == 0) && (rcnt == 0)) { /* Cmd + Addr + Dummy cycles */ ret = ifx_sscTx(dev->sflash_handler, buf, start, chain_queue, flag); if (ret != start) { err++; IFX_SFLASH_PRINT("line %d ifx_sscTx fails %d\n", __LINE__, ret); goto sflash_session_out; } } else if (wcnt > 0) { /* Cmd + Addr + Dummy cycles + Write data */ total = start + wcnt; memcpy(buf + start, wbuf, wcnt); ret = ifx_sscTx(dev->sflash_handler, buf, total, chain_queue, flag); if (ret != total) { err++; IFX_SFLASH_PRINT("line %d ifx_sscTx fails %d\n", __LINE__, ret); goto sflash_session_out; } } else if (rcnt > 0) { /* Cmd + Addr + Dummy cycles + Read data */ int rx_aligned = 0; total = start + rcnt; rx_aligned = (((u32)rbuf) & (IFX_SFLASH_DMA_MAX_BURST_LEN - 1)) == 0 ? 1 : 0; if (rx_aligned == 0){ tbuf = dev->flash_rx_buf; } else { tbuf = rbuf; } ret = ifx_sscTxRx(dev->sflash_handler, buf, start, tbuf, rcnt, chain_queue, flag); if (ret != total) { err++; IFX_SFLASH_PRINT("line %d ifx_sscTxRx fails %d\n", __LINE__, ret); goto sflash_session_out; } if (rx_aligned == 0) { memcpy(rbuf, tbuf, rcnt); } else { /* Do nothing */ } } else { printk("%s should never happen\n", __func__); } sflash_session_out: return err; } static INLINE int ifx_sflash_se(u8 *addr) { return ifx_sflash_session(spi_sflash, spi_sflash->erase_opcode, addr, 0, NULL, 0, NULL, 0, WRITE_ENABLE); } static INLINE int ifx_sflash_pp(u8 *addr, u8 *buf, u32 len) { u8 cmd = IFX_OPCODE_PP; return ifx_sflash_session(spi_sflash, cmd, addr, 0, buf, len, NULL, 0, WRITE_ENABLE); } static INLINE int ifx_sflash_rd(u8 *addr, u8 *buf, u32 len) { u8 cmd = IFX_OPCODE_READ; return ifx_sflash_session(spi_sflash, cmd, addr, spi_sflash->dummy_cycles, NULL, 0, buf, len, NO_WRITE_ENABLE); } static INLINE int ifx_spi_read(u32 saddr, u8 *buf, u32 len) { int ret; u8 addr[IFX_MAX_ADDRESS_NUM] = {0}; u32_to_u8_addr(saddr, addr); ret = ifx_sflash_rd(addr, buf, len); return ret; } static INLINE int ifx_spi_write(u32 saddr, u8 *buf,u32 len) { int ret; u8 addr[IFX_MAX_ADDRESS_NUM] = {0}; u32_to_u8_addr(saddr, addr); ret = ifx_sflash_pp(addr, buf, len); return ret; } static INLINE int ifx_spi_sector_erase(u32 saddr) { u8 addr[IFX_MAX_ADDRESS_NUM] = {0}; u32_to_u8_addr(saddr, addr); return ifx_sflash_se(addr); } static INLINE int ifx_spi_chip_erase(void) { u8 cmd = IFX_OPCODE_CHIP_ERASE; return ifx_sflash_session(spi_sflash, cmd, NULL, 0, NULL, 0, NULL, 0, WRITE_ENABLE); } /** * \fn static int ifx_spi_flash_probe(ifx_spi_dev_t *pdev) * \brief Detect serial flash device * * \param[in] pdev Pointer to ifx_spi_dev_t * \return -l Failed to detect device * \return 0 OK * \ingroup IFX_SFLASH_INTERNAL */ static int ifx_spi_flash_probe(ifx_spi_dev_t *pdev) { struct chain_queue_head *chain_queue; unsigned i; u16 dev_id; u8 cmd = IFX_OPCODE_RDID; /* Send the request for the part identification */ ifx_sscTx(pdev->sflash_handler, &cmd, sizeof (cmd), &chain_queue, CHAIN_QUEUE_START); /* Now read in the manufacturer id bytes */ do { ifx_sscRx(pdev->sflash_handler, &pdev->manufacturer_id, 1, &chain_queue, CHAIN_QUEUE_CONTINUE); if (pdev->manufacturer_id == 0x7F) { printk("Warning: unhandled manufacturer continuation byte!\n"); } } while (pdev->manufacturer_id == 0x7F); /* Now read in the first device id byte */ ifx_sscRx(pdev->sflash_handler, &pdev->device_id1, 1, &chain_queue, CHAIN_QUEUE_CONTINUE); /* Now read in the second device id byte */ ifx_sscRx(pdev->sflash_handler, &pdev->device_id2, 1, &chain_queue, CHAIN_QUEUE_FINISH); dev_id = (pdev->device_id1 << 8) | pdev->device_id2; IFX_SFLASH_PRINT("Vendor %02x Type %02x sig %02x\n", pdev->manufacturer_id, pdev->device_id1, pdev->device_id2); for (i = 0; i < ARRAY_SIZE(flash_manufacturers); ++i) { if (pdev->manufacturer_id == flash_manufacturers[i].id) { break; } } if (i == ARRAY_SIZE(flash_manufacturers)){ goto unknown; } pdev->manufacturer = &flash_manufacturers[i]; for (i = 0; pdev->manufacturer->flashes[i].id; ++i) { if (dev_id == pdev->manufacturer->flashes[i].id) { break; } } if (!pdev->manufacturer->flashes[i].id) { goto unknown; } pdev->flash = &pdev->manufacturer->flashes[i]; pdev->sector_size = pdev->flash->sector_size; pdev->num_sectors = pdev->flash->num_sectors; pdev->dummy_cycles = IFX_FAST_READ_DUMMY_BYTE; pdev->write_length = IFX_FLASH_PAGESIZE; pdev->size = pdev->sector_size * pdev->num_sectors; printk("SPI Device: %s 0x%02X (%s) 0x%02X 0x%02X\n", pdev->flash->name, pdev->manufacturer_id, pdev->manufacturer->name, pdev->device_id1, pdev->device_id2); IFX_SFLASH_PRINT(" Parameters: num sectors = %lu, sector size = %lu, " "write size = %u\n", pdev->num_sectors, pdev->sector_size, pdev->write_length); return 0; unknown: printk("Unknown SPI device: 0x%02X 0x%02X 0x%02X\n", pdev->manufacturer_id, pdev->device_id1, pdev->device_id2); return -1; } static INLINE int ifx_spi_flash_cs_handler(u32 csq, IFX_CS_DATA cs_data) { if (csq == IFX_SSC_CS_ON) { /* Low active */ return ifx_ssc_cs_low(cs_data); } else { return ifx_ssc_cs_high(cs_data); } } static INLINE void ifx_spi_flash_version(void) { char ver_str[128]; ifx_driver_version(ver_str, sizeof(ver_str), "SPI flash", IFX_SFLASH_VER_MAJOR, IFX_SFLASH_VER_MID, IFX_SFLASH_VER_MINOR); pr_info("%s\n", ver_str); } /** * \fn static int ifx_spi_flash_read(struct mtd_info *mtd, loff_t from, size_t len, * size_t *retlen ,u_char *buf) * \brief Read from the serial flash device. * * \param[in] mtd Pointer to struct mtd_info * \param[in] from Start offset in flash device * \param[in] len Amount to read * \param[out] retlen About of data actually read * \param[out] buf Buffer containing the data * \return 0 No need to read actually or read successfully * \return -EINVAL invalid read length * \ingroup IFX_SFLASH_OS */ static int ifx_spi_flash_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen ,u_char *buf) { size_t total = 0; size_t len_this_lp; u32 addr; u8 *mem; IFX_SFLASH_PRINT("(from = 0x%.8x, len = %d)\n",(u32) from, (int)len); if (!len || from < 0) return 0; if ((typeof(mtd->size))(from + len) > mtd->size) return -EINVAL; down(&ifx_sflash_sem); /* Fragment support */ while (total < len) { mem = (u8 *)(buf + total); addr = from + total; len_this_lp = min((len - total), (size_t)IFX_SFLASH_FRAGSIZE); ifx_spi_read(addr, mem, len_this_lp); total += len_this_lp; } *retlen = len; up(&ifx_sflash_sem); return 0; } /** * \fn static int ifx_spi_flash_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) * \brief Read from the serial flash device. * * \param[in] mtd Pointer to struct mtd_info * \param[in] to Start offset in flash device * \param[in] len Amount to write * \param[out] retlen Amount of data actually written * \param[out] buf Buffer containing the data * \return 0 No need to read actually or read successfully * \return -EINVAL invalid read length * \ingroup IFX_SFLASH_OS */ static int ifx_spi_flash_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { size_t total = 0, len_this_lp, bytes_this_page; u32 addr = 0; u8 *mem; IFX_SFLASH_PRINT("(to = 0x%.8x, len = %d)\n",(u32) to, len); if (retlen) { *retlen = 0; } /* sanity check */ if (len == 0) { return 0; } if ((typeof(mtd->size))(to + len) > mtd->size) { return (-1); } down(&ifx_sflash_sem); while(total < len) { mem = (u8 *)(buf + total); addr = to + total; bytes_this_page = spi_sflash->write_length - (addr % spi_sflash->write_length); len_this_lp = min((len - total), (size_t)bytes_this_page); ifx_spi_write(addr, mem, len_this_lp); total += len_this_lp; } *retlen = len; up(&ifx_sflash_sem); return 0; } static void ifx_spi_flash_sync(struct mtd_info *mtd) { BUG_ON(!spi_sflash); WARN_ON_ONCE(mtd->priv != spi_sflash); ifx_ssc_chain_finish(spi_sflash->sflash_handler, NULL); } /** * \fn static int ifx_spi_flash_erase(struct mtd_info *mtd,struct erase_info *instr) * \brief Erase pages of serial flash device. * * \param[in] mtd Pointer to struct mtd_info * \param[in] instr Pointer to struct erase_info * \return 0 OK * \return -EINVAL invalid erase size * \ingroup IFX_SFLASH_OS */ static int ifx_spi_flash_erase(struct mtd_info *mtd, struct erase_info *instr) { u32 addr,len; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) IFX_SFLASH_PRINT("(addr = 0x%llx, len = %lld)\n", (long long)instr->addr, (long long)instr->len); #else IFX_SFLASH_PRINT("(addr = 0x%.8x, len = %d)\n", instr->addr, instr->len); #endif if ((instr->addr + instr->len) > mtd->size) return (-EINVAL); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) { uint32_t rem; div_u64_rem(instr->len, mtd->erasesize, &rem); if (rem) { return -EINVAL; } } #else if ((instr->addr % mtd->erasesize) != 0 /* || (instr->len % mtd->erasesize) != 0*/) { return -EINVAL; } #endif addr = instr->addr; len = instr->len; down(&ifx_sflash_sem); /* whole-chip erase? */ if (len == mtd->size) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) IFX_SFLASH_PRINT("%lldKiB\n", (long long)(mtd->size >> 10)); #else IFX_SFLASH_PRINT("%ldKiB\n", (long)(mtd->size >> 10)); #endif if (ifx_spi_chip_erase() != 0) { instr->state = MTD_ERASE_FAILED; up(&ifx_sflash_sem); return -EIO; } } else { /* REVISIT in some cases we could speed up erasing large regions * by using OPCODE_SE instead of OPCODE_BE_4K. We may have set up * to use "small sector erase", but that's not always optimal. */ while (len) { if (ifx_spi_sector_erase(addr) != 0) { instr->state = MTD_ERASE_FAILED; up(&ifx_sflash_sem); return -EIO; } addr += mtd->erasesize; len -= mtd->erasesize; } } up(&ifx_sflash_sem); /* Inform MTD subsystem that erase is complete */ instr->state = MTD_ERASE_DONE; mtd_erase_callback(instr); IFX_SFLASH_PRINT("return\n"); return 0; } static INLINE IFX_SSC_HANDLE ifx_spi_flash_register(char *dev_name) { IFX_SSC_CONFIGURE_t ssc_cfg = {0}; ssc_cfg.baudrate = IFX_SFLASH_BAUDRATE; ssc_cfg.csset_cb = ifx_spi_flash_cs_handler; ssc_cfg.cs_data = IFX_SFLASH_CS; ssc_cfg.fragSize = IFX_SFLASH_FRAGSIZE; ssc_cfg.maxFIFOSize = IFX_SFLASH_MAXFIFOSIZE; ssc_cfg.ssc_mode = IFX_SFLASH_MODE; ssc_cfg.ssc_prio = IFX_SFLASH_PRIORITY; ssc_cfg.duplex_mode = IFX_SSC_HALF_DUPLEX; return ifx_sscAllocConnection(dev_name, &ssc_cfg); } static INLINE int ifx_spi_flash_size_to_index(u32 size) { int i; int index = IFX_FLASH_128KB; i = (size >> 17); /* 128 KB minimum */ if (i <= 1) { index = IFX_FLASH_128KB; } else if (i <= 2) { index = IFX_FLASH_256KB; } else if (i <= 4) { index = IFX_FLASH_512KB; } else if (i <= 8) { index = IFX_FLASH_1MB; } else if (i <= 16) { index = IFX_FLASH_2MB; } else if (i <= 32) { index = IFX_FLASH_4MB; } else if (i <= 64) { index = IFX_FLASH_8MB; } else if (i <= 128) { index = IFX_FLASH_16MB; } else { index = IFX_SPI_MAX_FLASH; } return index; } static INLINE void ifx_spi_flash_gpio_init(void) { ifx_gpio_register(IFX_GPIO_MODULE_SPI_FLASH); } static INLINE void ifx_spi_flash_gpio_release(void) { ifx_gpio_deregister(IFX_GPIO_MODULE_SPI_FLASH); } static int __init ifx_spi_flash_init (void) { IFX_SSC_HANDLE *sflash_handler; struct mtd_info *mtd; #ifdef CONFIG_MTD_CMDLINE_PARTS int np; #endif /* CONFIG_MTD_CMDLINE_PARTS */ int ret = 0; int index; sema_init(&ifx_sflash_sem, 1); ifx_spi_flash_gpio_init(); spi_sflash = (ifx_spi_dev_t *)kmalloc(sizeof(ifx_spi_dev_t), GFP_KERNEL); if (spi_sflash == NULL) { ret = -ENOMEM; goto done; } memset(spi_sflash, 0, sizeof (ifx_spi_dev_t)); /* * Make sure tx buffer address is DMA burst length aligned and 2 page size< 512> should be enouhg for * serial flash. In this way, host cpu can make good use of DMA operation. */ spi_sflash->flash_tx_org_buf = kmalloc(IFX_SFLASH_MAX_WRITE_SIZE + IFX_SFLASH_DMA_MAX_BURST_LEN - 1, GFP_KERNEL); if (spi_sflash->flash_tx_org_buf == NULL) { ret = -ENOMEM; goto err1; } spi_sflash->flash_tx_buf = (char *)(((u32)(spi_sflash->flash_tx_org_buf + IFX_SFLASH_DMA_MAX_BURST_LEN - 1)) & ~(IFX_SFLASH_DMA_MAX_BURST_LEN - 1)); /* * Make sure rx buffer address is DMA burst length aligned and 8 page size< 2KB> should be enouhg for * serial flash. In this way, host cpu can make good use of DMA operation. */ spi_sflash->flash_rx_org_buf = kmalloc(IFX_SFLASH_MAX_READ_SIZE + IFX_SFLASH_DMA_MAX_BURST_LEN - 1, GFP_KERNEL); if (spi_sflash->flash_rx_org_buf == NULL) { ret = -ENOMEM; goto err2; } spi_sflash->flash_rx_buf = (char *)(((u32)(spi_sflash->flash_rx_org_buf + IFX_SFLASH_DMA_MAX_BURST_LEN - 1)) & ~(IFX_SFLASH_DMA_MAX_BURST_LEN - 1)); spi_sflash->addr_cycles = IFX_SFLASH_ADDR_CYCLES; mtd = kmalloc(sizeof(struct mtd_info), GFP_KERNEL); if ( mtd == NULL ) { printk(KERN_WARNING "%s Cant allocate mtd stuff\n", __func__); ret = -ENOMEM; goto err3; } memset(mtd, 0, sizeof(struct mtd_info)); sflash_handler = ifx_spi_flash_register(IFX_SFLASH_NAME); if (sflash_handler == NULL) { printk("%s: failed to register sflash\n", __func__); ret = -ENOMEM; goto err4; } spi_sflash->sflash_handler = sflash_handler; if (ifx_spi_flash_probe(spi_sflash) != 0) { printk(KERN_WARNING "%s: Found no serial flash device\n", __func__); ret = -ENXIO; goto err5; } mtd->priv = spi_sflash; mtd->name = IFX_SFLASH_NAME; mtd->type = MTD_NORFLASH; mtd->flags = (MTD_CAP_NORFLASH | MTD_WRITEABLE); mtd->size = spi_sflash->size; /* Prefer "small sector" erase if possible */ if (spi_sflash->flash->flags & SECT_4K) { spi_sflash->erase_opcode = IFX_OPCODE_BE_4K; mtd->erasesize = 4096; } else { spi_sflash->erase_opcode = IFX_OPCODE_SE; mtd->erasesize = spi_sflash->sector_size; } mtd->numeraseregions = 0; mtd->eraseregions = NULL; #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,41) mtd->module = THIS_MODULE; #else mtd->owner = THIS_MODULE; #endif #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,16) mtd->writesize = 1; /* like NOR flash, should be 1 */ #endif mtd->_erase = ifx_spi_flash_erase; mtd->_read = ifx_spi_flash_read; mtd->_write = ifx_spi_flash_write; mtd->_sync = ifx_spi_flash_sync; index = ifx_spi_flash_size_to_index(spi_sflash->size); #if defined(CONFIG_TFFS_DEV_MTDNOR) || defined(CONFIG_TFFS_DEV_LEGACY) TFFS3_Register_Panic_CB(mtd, panic_reinit); #endif if (index > IFX_SPI_MAX_FLASH) { printk (KERN_WARNING "%s: flash size is too big to support\n", __func__); ret = -EINVAL; goto err5; } #ifdef IFX_SPI_FLASH_DBG printk (KERN_DEBUG "mtd->name = %s\n" "mtd->size = 0x%.8llx (%lluM)\n" "mtd->erasesize = 0x%.8x (%uK)\n" "mtd->numeraseregions = %d\n" "mtd index %d\n", mtd->name, mtd->size, mtd->size / (1024*1024), mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions, index); if (mtd->numeraseregions) { int result; for (result = 0; result < mtd->numeraseregions; result++) { printk (KERN_DEBUG "\n\n" "mtd->eraseregions[%d].offset = 0x%.8llx\n" "mtd->eraseregions[%d].erasesize = 0x%.8x (%uK)\n" "mtd->eraseregions[%d].numblocks = %d\n", result, mtd->eraseregions[result].offset, result, mtd->eraseregions[result].erasesize, mtd->eraseregions[result].erasesize / 1024, result, mtd->eraseregions[result].numblocks); } } #endif /* IFX_SPI_FLASH_DBG */ #ifdef CONFIG_MTD_CMDLINE_PARTS np = parse_mtd_partitions(mtd, part_probes, &spi_sflash->parsed_parts, 0); if (np > 0) { mtd_device_register(mtd, spi_sflash->parsed_parts, np); } else { printk(KERN_ERR "%s: No valid partition table found in command line\n", __func__); goto err5; } #else mtd_device_register(mtd, ifx_spi_partitions, IFX_MTD_SPI_PARTS); #endif /* CONFIG_MTD_CMDLINE_PARTS */ spi_sflash->mtd = mtd; ifx_spi_flash_version(); return ret; err5: ifx_sscFreeConnection(spi_sflash->sflash_handler); err4: kfree(mtd); err3: kfree(spi_sflash->flash_rx_org_buf); err2: kfree(spi_sflash->flash_tx_org_buf); err1: kfree(spi_sflash); done: ifx_spi_flash_gpio_release(); return ret; } static void __exit ifx_spi_flash_exit(void) { if (spi_sflash != NULL) { if (spi_sflash->parsed_parts != NULL) { mtd_device_unregister(spi_sflash->mtd); } kfree(spi_sflash->mtd); ifx_sscFreeConnection(spi_sflash->sflash_handler); kfree(spi_sflash->flash_rx_org_buf); kfree(spi_sflash->flash_tx_org_buf); kfree(spi_sflash); } ifx_spi_flash_gpio_release(); } module_init(ifx_spi_flash_init); module_exit(ifx_spi_flash_exit); #if defined(CONFIG_TFFS_DEV_MTDNOR) || defined(CONFIG_TFFS_DEV_LEGACY) #define FLASH_BUFFER_SIZE 256U struct _spi_register *const SPI = (struct _spi_register *)&(*(volatile unsigned int *)(IFX_SSC_PHY_BASE | KSEG1)); static DEFINE_SPINLOCK(panic_locking); /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline void panic_lock(unsigned long *flags, unsigned long *cpuflag __maybe_unused) { spin_lock_irqsave(&panic_locking, *flags); #if defined(CONFIG_SMP) *cpuflag = dvpe(); #endif/*--- #if defined(CONFIG_SMP) ---*/ } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline void panic_unlock(unsigned long flags, unsigned long cpuflag __maybe_unused) { #if defined(CONFIG_SMP) evpe(cpuflag); #endif/*--- #if defined(CONFIG_SMP) ---*/ spin_unlock_irqrestore(&panic_locking, flags); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline int panic_busy_wait(void) { unsigned int usec = 1000; while(SPI->STAT.Bits.bsy && --usec){ udelay(1); } return SPI->STAT.Bits.bsy ? 1 : 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline int panic_read_wait(void) { unsigned int usec = 1000; while((SPI->FSTAT.Bits.rxffl < 1) && --usec){ udelay(1); } return SPI->FSTAT.Bits.rxffl ? 0 : 1; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void panic_cs(enum spi_cs cs, enum spi_cs_enum set) { switch(set){ case deselect: /*--- set CS high ---*/ SPI->FGPO = (1 << cs) << 8; break; case select: /*--- set CS low ---*/ SPI->FGPO = (1 << cs); break; } } /*------------------------------------------------------------------------------------------*\ * es muss RX & TX eingeschaltet sein, damit panic_cmd_simple funktioniert \*------------------------------------------------------------------------------------------*/ static uint8_t panic_cmd_simple(unsigned int cmd) { uint8_t rxoff, txoff, tmp; tmp = 0xff; rxoff = SPI->CON.Bits.rxoff; txoff = SPI->CON.Bits.txoff; SPI->CON.Bits.rxoff = 0; SPI->CON.Bits.txoff = 0; panic_cs(IFX_SSC_CSx, select); SPI->TB.Char.Byte = cmd; if(panic_read_wait()){ printk(KERN_ERR"%s error on panic_read_wait\n", __func__); goto err_out; } tmp = SPI->RB & 0xFF; err_out: panic_cs(IFX_SSC_CSx, deselect); mdelay(1); SPI->CON.Bits.rxoff = rxoff; SPI->CON.Bits.txoff = txoff; return tmp; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static unsigned char panic_read_status(enum spi_cs cs) { uint8_t rxoff, txoff, tmp; tmp = 0xff; rxoff = SPI->CON.Bits.rxoff; txoff = SPI->CON.Bits.txoff; SPI->CON.Bits.rxoff = 0; SPI->CON.Bits.txoff = 0; panic_cs(cs, select); SPI->TB.Short.Bytes = SPI_READ_STATUS << 8; if(panic_read_wait()){ goto err_out; } tmp = SPI->RB & 0xFF; err_out: panic_cs(cs, deselect); mdelay(1); SPI->CON.Bits.rxoff = rxoff; SPI->CON.Bits.txoff = txoff; return tmp; } static int panic_do_read(struct mtd_info *mtd __maybe_unused, loff_t addr, size_t len, size_t *retlen, u_char *buf) { int result; size_t chunk_len; uint32_t tmp_buf; // pr_err("[%s] Called. addr: 0x%08llx len: 0x%08x\n", __func__, addr, len); SPI->CON.Bits.rxoff = 1; SPI->CON.Bits.txoff = 0; panic_cs(IFX_SSC_CSx, select); SPI->TB.Register = SPI_READ | addr; result = 0; if(panic_busy_wait()){ printk(KERN_ERR"%s: error SPI busy %d\n", __func__, __LINE__); result = -EBUSY; goto err_out; } SPI->CON.Bits.txoff = 1; SPI->CON.Bits.rxoff = 0; *retlen = 0; while(len){ chunk_len = min(sizeof(uint32_t), len); SPI->RXREQ = chunk_len; if(panic_read_wait()){ printk("%s error on panic_read_wait\n", __func__); result = -ETIMEDOUT; goto err_out; } udelay(5); tmp_buf = SPI->RB; tmp_buf <<= (8 * (sizeof(uint32_t) - chunk_len)); memcpy(buf, &tmp_buf, chunk_len); len -= chunk_len; buf += chunk_len; *retlen += chunk_len; } err_out: panic_cs(IFX_SSC_CSx, deselect); SPI->CON.Bits.rxoff = 0; SPI->CON.Bits.txoff = 0; return result; } static int panic_read(struct mtd_info *mtd __maybe_unused, loff_t addr, size_t len, size_t *retlen, u_char *buf) { unsigned long flags, cpuflags; int result; panic_lock(&flags, &cpuflags); result = panic_do_read(mtd, addr, len, retlen, buf); panic_unlock(flags, cpuflags); return result; } /*------------------------------------------------------------------------------------------*\ * program buffer to page. Buffer must be uint32_t aligned and addr + len must * not cross a page boundary \*------------------------------------------------------------------------------------------*/ static int panic_write_page(uint32_t addr, size_t len, const uint8_t *buf) { enum spi_cs cs = IFX_SSC_CSx; int status, result; unsigned int cnt; result = 0; SPI->CON.Bits.rxoff = 1; panic_cmd_simple(SPI_WRITE_ENABLE); panic_cs(cs, select); SPI->TB.Register = SPI_PAGE_PROGRAM | addr; if(panic_busy_wait()){ printk(KERN_ERR"%s: error SPI busy %d\n", __func__, __LINE__); result = -ETIMEDOUT; goto err_out; } while(len >= sizeof(uint32_t)){ SPI->TB.Register = *(uint32_t *) buf; if(panic_busy_wait()){ printk(KERN_ERR"%s: error SPI busy %d\n", __func__, __LINE__); result = -EIO; goto err_out; } buf += sizeof(uint32_t); len -= sizeof(uint32_t); } if(len >= sizeof(uint16_t)){ SPI->TB.Short.Bytes = *(uint16_t *) buf; if(panic_busy_wait()){ printk(KERN_ERR"%s: error SPI busy %d\n", __func__, __LINE__); result = -EIO; goto err_out; } buf += sizeof(uint16_t); len -= sizeof(uint16_t); } if(len > 0){ SPI->TB.Char.Byte = *(uint8_t *) buf; if(panic_busy_wait()){ printk(KERN_ERR"%s: error SPI busy %d\n", __func__, __LINE__); result = -EIO; goto err_out; } } panic_cs(cs, deselect); SPI->CON.Bits.rxoff = 0; cnt = 1000; do{ if(--cnt == 0){ pr_err("[%s:%d] error on status %x chip_addr=%x datalen=%u\n", __func__, __LINE__, status, addr, len); result = -EIO; goto err_out; } udelay(1); status = panic_read_status(cs); } while(!(status & (WIP | WEL))); err_out: panic_cs(IFX_SSC_CSx, deselect); SPI->CON.Bits.rxoff = 0; SPI->CON.Bits.txoff = 0; return result; } static int panic_do_write(struct mtd_info *mtd, loff_t addr, size_t len, size_t *retlen, const uint8_t *buf) { uint8_t pagebuf[FLASH_BUFFER_SIZE + sizeof(uint32_t)], *pbuf; int result; size_t chunk_len, read_len, written; // TODO: clean up this check if(addr < 0x20000){ pr_emerg("[%s] Not writing to bootloader address 0x%08llx\n", __func__, addr); } result = 0; *retlen = 0; written = 0; pbuf = &(pagebuf[0]); pbuf = PTR_ALIGN(pbuf, sizeof(uint32_t)); while(written < len){ chunk_len = min((len - written), FLASH_BUFFER_SIZE); chunk_len = min(chunk_len, (FLASH_BUFFER_SIZE - ((addr + written) & (FLASH_BUFFER_SIZE - 1)))); memcpy(pbuf, &(buf[written]), chunk_len); result = panic_write_page(addr + written, chunk_len, pbuf); if(result != 0){ goto err_out; } written += chunk_len; } *retlen = written; written = 0; while(written < len){ chunk_len = min((len - written), FLASH_BUFFER_SIZE); memset(pbuf, 0x0, FLASH_BUFFER_SIZE); result = panic_do_read(mtd, addr + written, chunk_len, &read_len, pbuf); if(result != 0 || read_len != chunk_len){ pr_err("[%s] verify read at 0x%08llx failed: chunk_len: 0x%08x read_len: 0x%08x\n", __func__, addr + written, chunk_len, read_len); result = -EIO; goto err_out; } if(memcmp(pbuf, &buf[written], chunk_len)){ pr_err("[%s] verify compare at 0x%08llx failed\n", __func__, addr + written); print_hex_dump(KERN_ERR, "orig: ", DUMP_PREFIX_OFFSET, 16, 4, &buf[written], chunk_len, 0); pr_err("\n"); print_hex_dump(KERN_ERR, "read: ", DUMP_PREFIX_OFFSET, 16, 4, pbuf, chunk_len, 0); result = -EIO; goto err_out; } written += chunk_len; } err_out: return result; } static int panic_write(struct mtd_info *mtd, loff_t addr, size_t len, size_t *retlen, const uint8_t *buf) { unsigned long flags, cpuflags; int result; panic_lock(&flags, &cpuflags); result = panic_do_write(mtd, addr, len, retlen, buf); panic_unlock(flags, cpuflags); return result; } static void panic_sync(struct mtd_info *mtd __maybe_unused) { } static int panic_erase(struct mtd_info *mtd __maybe_unused, struct erase_info *info __maybe_unused) { return -EROFS; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static struct mtd_info *panic_reinit(struct mtd_info *mtd) { unsigned long flags, cpuflags; unsigned int flash_data; unsigned int rmc, ssc_clock, br; pr_err("[%s] Called for mtd %s\n", __func__, mtd->name); panic_lock(&flags, &cpuflags); if(down_trylock(&ifx_sflash_sem)){ return NULL; } /* SSC0 Ports */ /* P0.10/P0.15 as CS4/CS1 for flash or eeprom depends on jumper */ /* P0.10 ALT0= 0, ALT1=1, DIR=1 */ *(IFX_GPIO_P0_DIR) |= BIT(SPI_CSx); #if defined(CONFIG_VR9) *(IFX_GPIO_P0_ALTSEL0) &= ~BIT(SPI_CSx); *(IFX_GPIO_P0_ALTSEL1) |= BIT(SPI_CSx); #elif defined(CONFIG_AR10)/*--- #if defined(CONFIG_VR9) ---*/ *(IFX_GPIO_P0_ALTSEL0) |= BIT(SPI_CSx); *(IFX_GPIO_P0_ALTSEL1) &= ~BIT(SPI_CSx); #else #error unknown platform #endif *(IFX_GPIO_P0_OD) |= BIT(SPI_CSx); /* p1.0 SPI_DIN, p1.1 SPI_DOUT, p1.2 SPI_CLK */ *(IFX_GPIO_P1_DIR) |= (BIT(SPI_DOUT) | BIT(SPI_CLK)); *(IFX_GPIO_P1_DIR) &= ~(BIT(SPI_DIN)); *(IFX_GPIO_P1_ALTSEL0) |= (BIT(SPI_DOUT) | BIT(SPI_CLK) | BIT(SPI_DIN)); *(IFX_GPIO_P1_ALTSEL1) &= ~(BIT(SPI_DOUT) | BIT(SPI_CLK) | BIT(SPI_DIN)); *(IFX_GPIO_P1_OD) |= (BIT(SPI_DOUT) | BIT(SPI_CLK)); /* Clock Control Register *//* DISS OFF and RMC = 1 */ /* Disable SSC to get access to the control bits */ SPI->WHBSTATE = 1; /*--- Disable SSC ; ---*/ asm("SYNC"); rmc = SPI->CLC >> 8; if(rmc == 0){ rmc = 1; SPI->CLC = (rmc << 8); } /*CSx */ SPI->GPOCON = (1 << (IFX_SSC_CSx + 8)); panic_cs(IFX_SSC_CSx, deselect); /* disable IRQ */ SPI->IRNEN = 0; /*--- *AMAZON_S_SSC1_IRNEN = 0x0; ---*/ /*------------------------------------------------------------------------------------------*\ * Set the Baudrate * BR = (FPI clk / (2 * Baudrate)) - 1 * Note: Must be set while SSC is disabled! \*------------------------------------------------------------------------------------------*/ ssc_clock = ifx_get_fpi_hz() / rmc; br = (((ssc_clock >> 1) + SFLASH_BAUDRATE / 2) / SFLASH_BAUDRATE) - 1; SPI->BRT = br; /*enable and flush RX/TX FIFO*/ SPI->RXFCON.Register = (0xF << 8) | (1 << 1) | (1 << 0); SPI->TXFCON.Register = (0xF << 8) | (1 << 1) | (1 << 0); /* set CON, TX off , RX off, ENBV=0, BM=7(8 bit valid) HB=1(MSB first), PO=0,PH=1(SPI Mode 0)*/ SPI->CON.Register = CON_ENBV | CON_PH | CON_HB | CON_RXOFF | CON_TXOFF; /*--- 0x00070033 ---*/ asm("SYNC"); /*Set Master mode and Enable SSC */ SPI->WHBSTATE = (1 << 3) | (1 << 1); asm("SYNC"); SPI->CON.Bits.rxoff = 0; SPI->CON.Bits.txoff = 0; panic_cs(IFX_SSC_CSx, select); SPI->TB.Register = SPI_READ_ELECTRONIC_ID_MANUFACTURE << 24; while(SPI->STAT.Bits.rxbv < 4) ; panic_cs(IFX_SSC_CSx, deselect); flash_data = SPI->RB; panic_read_status(IFX_SSC_CSx); panic_unlock(flags, cpuflags); mtd->_read = panic_read; mtd->_write = panic_write; mtd->_sync = panic_sync; mtd->_erase = panic_erase; return mtd; } #endif // defined(CONFIG_TFFS_DEV_MTDNOR) || defined(CONFIG_TFFS_DEV_LEGACY) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Chuanhua.Lei@infineon.com"); MODULE_SUPPORTED_DEVICE("Serial flash 25 types generic driver"); MODULE_DESCRIPTION("IFAP SPI flash device driver");