/* * vx185_snor.c -- Ikanox VX185 SPI NOR flash controller driver * * Author: Tido Klaassen * Copyright (C) 2012 AVM GmbH. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRIVER_DESC "Ikanos VX185 SPI NOR Flash driver" #define DRIVER_NAME "vx185_snor" #define DRIVER_VERSION "0.0.1" #define RX_FIFO_SIZE 64 #define TX_FIFO_SIZE 64 struct flash_layout { unsigned int size; unsigned int block_size; unsigned int sector_size; unsigned int page_size; }; #define MANUFACT_ID_MACRONIX 0xC2 #define MANUFACT_ID_SPANSION 0x01 struct flash_layout layout_macronix = { .size = 16 << 20, .block_size = 64 << 10, .sector_size = 4 << 10, .page_size = 256 }; struct flash_layout layout_spansion = { .size = 4 << 20, .block_size = 64 << 10, .sector_size = 64 << 10, .page_size = 256 }; struct flash_layout *layout; /* serial flash command table */ struct sflash_cmd_table { unsigned char r; /* read */ unsigned char fr; /* fast read */ unsigned char dfr; /* dual fast read */ unsigned char rdid; /* read device id */ unsigned char wren; /* write enable */ unsigned char wrdi; /* write disable */ unsigned char se; /* sector erase */ unsigned char be; /* block erase */ unsigned char ce; /* chip erase */ unsigned char pp; /* page program */ unsigned char cp; /* continuously program whole chip */ unsigned char rdsr; /* read status reg */ unsigned char wrsr; /* write status reg */ unsigned char dp; /* deep power down */ unsigned char rd; /* release deep power down */ unsigned char res; /* read electronic device id */ }; /* default serial flash command table */ static const struct sflash_cmd_table macronix_cmd_table = { .r = 0x03, .fr = 0x0b, .dfr = 0x0b, /* No dual read support */ .rdid = 0x9f, .wren = 0x06, .wrdi = 0x04, .se = 0x20, .be = 0xd8, .ce = 0xc7, .pp = 0x02, .rdsr = 0x05, .wrsr = 0x01, .dp = 0xb9, .rd = 0xab, .res = 0xab }; static const struct sflash_cmd_table spansion_cmd_table = { .r = 0x03, .fr = 0x0b, .dfr = 0x0b, /* No dual read support */ .rdid = 0x9f, .wren = 0x06, .wrdi = 0x04, .se = 0xd8, .be = 0xd8, .ce = 0xc7, .pp = 0x02, .rdsr = 0x05, .wrsr = 0x01, .dp = 0xb9, .rd = 0xab, .res = 0xab }; static const struct sflash_cmd_table *cmd_table; static struct resource *spi_registers; static struct resource *spi_rx_fifo; static struct resource *spi_tx_fifo; static void __iomem *registers_ptr; static void __iomem *rx_ptr; static void __iomem *tx_ptr; static void vx185_snor_wait(void); static int vx185_snor_sector_erase(u_int32_t addr, unsigned char cmd); static int vx185_snor_wren(void); static int vx185_snor_rdsr(unsigned int *sr); static DECLARE_MUTEX(vx185_snor_sem); static unsigned int panic_mode = 0; /* GLOBAL FUNCTIONS */ static void vx185_snor_spi_down(void) { if(!panic_mode){ down(&vx185_snor_sem); } } static void vx185_snor_spi_up(void) { if(!panic_mode){ up(&vx185_snor_sem); } } static int vx185_snor_rdsr(unsigned int *sr) { int status; u_int32_t reg, port_conf; vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); reg |= (SPI0_GLOB_CONFIG_CE | SPI0_GLOB_CONFIG_SPI_CLK_POL); reg &= ~SPI0_GLOB_CONFIG_PD; iowrite32(reg, registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); iowrite32((cmd_table->rdsr & SPI0_SPI_INSTR_MASK), registers_ptr + VX185_SPI0_SPI_INSTR_OFF); iowrite32(1, registers_ptr + VX185_SPI0_BLOCK_SIZE_OFF); iowrite32(0, registers_ptr + VX185_SPI0_SPI_ADDR_OFF); iowrite32(SPI0_INT_TRNF_ERR, registers_ptr + VX185_SPI0_INT_EN_OFF); iowrite32(0, registers_ptr + VX185_SPI0_STATUS_OFF); iowrite32(0, registers_ptr + VX185_SPI0_INT_STATUS_OFF); port_conf = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~(SPI0_PORT_CONFIG_DEV_SEL_MASK | SPI0_PORT_CONFIG_IE); port_conf |= SPI0_PORT_CONFIG_START; iowrite32(port_conf, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); status = 0; if(ioread32(registers_ptr + VX185_SPI0_INT_STATUS_OFF) & SPI0_INT_TRNF_ERR){ status = -EFAULT; pr_err("[%s] Reading RSR failed!\n", __func__); } else { *sr = ioread32(registers_ptr + VX185_SPI0_DEV_RSR_OFF) & 0xff; } return status; } static void vx185_snor_reset_fifos(void) { u_int32_t reg; vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_RF_RST; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); while(!(ioread32(registers_ptr + VX185_SPI0_STATUS_OFF) & SPI0_STATUS_RF_RST_DONE)){ // pr_err("[%s] Waiting for RX_FIFO reset completion\n", __func__); if(!panic_mode){ schedule(); } } reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~SPI0_PORT_CONFIG_RF_RST; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_TF_RST; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); while(!(ioread32(registers_ptr + VX185_SPI0_STATUS_OFF) & SPI0_STATUS_TF_RST_DONE)){ // pr_err("[%s] Waiting for TX_FIFO reset completion\n", __func__); if(!panic_mode){ schedule(); } } reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~SPI0_PORT_CONFIG_TF_RST; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); } static int vx185_snor_erase(struct mtd_info *mtd, struct erase_info *instr) { unsigned int start, end, sec_start, blk_start, blk_end; int status; start = instr->addr; end = start + instr->len - 1; // we need to erase whole sectors end |= (layout->sector_size -1); if (end >= mtd->size){ return -EINVAL; } vx185_snor_spi_down(); instr->state = MTD_ERASING; instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; do { sec_start = start & ~(layout->sector_size -1); blk_start = start & ~(layout->block_size - 1); blk_end = blk_start | (layout->block_size - 1); if(sec_start == blk_start && end >= blk_end){ // erase region covers the whole next block pr_debug("[%s] Erasing Block %#x\n", __func__, blk_start); status = vx185_snor_sector_erase(blk_start, cmd_table->be); start += layout->block_size; } else { pr_debug("[%s] Erasing Sector %#x\n", __func__, sec_start); status = vx185_snor_sector_erase(sec_start, cmd_table->se); start += layout->sector_size; } if(status != 0){ instr->fail_addr = sec_start; instr->state = MTD_ERASE_FAILED; } } while (start < end && status == 0); if(status == 0){ instr->state = MTD_ERASE_DONE; } vx185_snor_spi_up(); if (instr->callback) { instr->callback(instr); } return 0; } static int vx185_snor_read_real(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen ,u_char *buf) { u_int32_t mybuf[RX_FIFO_SIZE / sizeof(u_int32_t)] __attribute__ ((aligned (__BIGGEST_ALIGNMENT__))) ; volatile u_int32_t reg, int_status, received; if(len > RX_FIFO_SIZE){ pr_err("[%s] len %d > RX_FIFO_SIZE\n", __func__, len); *retlen = 0; return -EINVAL; } vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); reg &= ~(SPI0_GLOB_CONFIG_PD | SPI0_GLOB_CONFIG_AHB_RESP_CTRL_MASK); reg |= (SPI0_GLOB_CONFIG_CE | SPI0_GLOB_CONFIG_SPI_CLK_POL); iowrite32(reg, registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); vx185_snor_reset_fifos(); iowrite32((cmd_table->r & SPI0_SPI_INSTR_MASK), registers_ptr + VX185_SPI0_SPI_INSTR_OFF); /* * controller hangs if transfer size is n * 64 + x (n >= 1, 1 <= x <= 4) * or 2-3 bytes. To make sure this doesn't happen, we round up to the next * multiple of RX_FIFO_SIZE and later discard the extra bytes. */ iowrite32(RX_FIFO_SIZE, registers_ptr + VX185_SPI0_BLOCK_SIZE_OFF); iowrite32((from & SPI0_SPI_ADDR_MASK), registers_ptr + VX185_SPI0_SPI_ADDR_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~(SPI0_PORT_CONFIG_DEV_SEL_MASK | SPI0_PORT_CONFIG_IE); iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); iowrite32((SPI0_INT_TRNF_ERR | SPI0_INT_RF_EMPTY), registers_ptr + VX185_SPI0_INT_EN_OFF); vx185_snor_wait(); iowrite32(0, registers_ptr + VX185_SPI0_INT_STATUS_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_START; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); int_status = ioread32(registers_ptr + VX185_SPI0_INT_STATUS_OFF); if(int_status & SPI0_INT_TRNF_ERR){ pr_err("[%s] SPI transfer error.\n", __func__); *retlen = 0; return -EIO; } received = 0; while(!(ioread32(registers_ptr + VX185_SPI0_INT_STATUS_OFF) & SPI0_INT_RF_EMPTY) && (received < ARRAY_SIZE(mybuf))){ mybuf[received] = ioread32(rx_ptr); ++received; } if(received > 0){ memcpy(buf, &(mybuf[0]), len); } *retlen = len; return 0; } static int vx185_snor_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen ,u_char *buf) { int result; size_t chunkread, chunk, remainder, done; *retlen = 0; if (len == 0){ return 0; } /* * check MTD boundaries and catch length that would lead * to infinite read operation */ if(from + len >= mtd->size || len >= 0xffffffff){ return -EINVAL; } vx185_snor_spi_down(); result = 0; // print_hex_dump(KERN_ERR, __func__, DUMP_PREFIX_OFFSET, 16, 1, buf, len, 1); done = 0; remainder = len; while(result == 0 && remainder > 0){ chunk = min(remainder, (size_t) RX_FIFO_SIZE); result = vx185_snor_read_real(mtd, from + done, chunk, &chunkread, buf + done); done += chunkread; remainder -= chunkread; } *retlen = done; // result = vx185_snor_read_real(mtd, from, len, retlen, buf); // print_hex_dump(KERN_ERR, __func__, DUMP_PREFIX_OFFSET, 16, 1, buf, len, 1); vx185_snor_spi_up(); return result; } static int vx185_snor_write_page(loff_t to, size_t len, size_t *retlen, const u_char *buf) { size_t total, chunksize; int result; u_int32_t reg, int_status, sent, padding, rdsr; u_int32_t mybuf[TX_FIFO_SIZE / sizeof(u_int32_t)] __attribute__ ((aligned (__BIGGEST_ALIGNMENT__))) ; *retlen = 0; vx185_snor_wait(); do{ result = vx185_snor_rdsr(&rdsr); } while(result == 0 && (rdsr & SPI0_DEV_RSR_WIP)); // pr_err("[%s] rdsr: %#x\n", __func__, rdsr); if(rdsr < 0){ return result; } vx185_snor_wait(); result = vx185_snor_wren(); if(result != 0){ return result; } vx185_snor_wait(); result = vx185_snor_rdsr(&rdsr); // pr_err("[%s] rdsr: %#x\n", __func__, rdsr); vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); reg |= (SPI0_GLOB_CONFIG_CE | SPI0_GLOB_CONFIG_SPI_CLK_POL); reg &= ~SPI0_GLOB_CONFIG_PD; iowrite32(reg, registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); vx185_snor_wait(); iowrite32((cmd_table->pp & SPI0_SPI_INSTR_MASK), registers_ptr + VX185_SPI0_SPI_INSTR_OFF); iowrite32(len, registers_ptr + VX185_SPI0_BLOCK_SIZE_OFF); iowrite32((to & SPI0_SPI_ADDR_MASK), registers_ptr + VX185_SPI0_SPI_ADDR_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~(SPI0_PORT_CONFIG_DEV_SEL_MASK | SPI0_PORT_CONFIG_IE); iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_reset_fifos(); iowrite32((SPI0_INT_TRNF_ERR | SPI0_INT_TF_FULL), registers_ptr + VX185_SPI0_INT_EN_OFF); total = 0; result = 0; // how many 32 bit words can we write in one go? if(sizeof(mybuf) <= (len - total)){ chunksize = ARRAY_SIZE(mybuf); padding = 0; } else { // round up size to the next complete word chunksize = (len - total) / sizeof(mybuf[0]); padding = (len - total) % sizeof(mybuf[0]); if(padding != 0){ ++chunksize; padding = sizeof(mybuf[0]) - padding; // pr_err("[%s] padding: %d\n", __func__, padding); } } memcpy(mybuf, buf + total, chunksize * sizeof(mybuf[0])); iowrite32(0, registers_ptr + VX185_SPI0_STATUS_OFF); iowrite32(0, registers_ptr + VX185_SPI0_INT_STATUS_OFF); sent = 0; while(sent < chunksize && !(ioread32(registers_ptr + VX185_SPI0_INT_STATUS_OFF) & SPI0_INT_TF_FULL)){ iowrite32(mybuf[sent], tx_ptr); ++sent; } reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_START; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); int_status = ioread32(registers_ptr + VX185_SPI0_INT_STATUS_OFF); if(int_status & SPI0_INT_TRNF_ERR){ result = -EIO; } if(result != 0){ return result; } vx185_snor_wait(); do{ result = vx185_snor_rdsr(&rdsr); } while(result == 0 && (rdsr & (SPI0_DEV_RSR_WIP | SPI0_DEV_RSR_WEL))); if(rdsr & SPI0_DEV_RSR_WEL){ return -EAGAIN; } *retlen = (sent * sizeof(mybuf[0])) - padding; return result; } static int vx185_snor_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { int total, chunksize, result, tries; size_t written; u_int32_t addr, page_offset; u_char *mem; u_char foo[TX_FIFO_SIZE]; if(to + len >= mtd->size){ return -EINVAL; } vx185_snor_spi_down(); result = 0; total = 0; tries = 0; while(total < len && result == 0 && tries < 3) { mem = (u_char *)(buf + total); addr = to + total; page_offset = addr & (layout->page_size - 1); chunksize = min((layout->page_size - page_offset), (len - total)); chunksize = min(chunksize, TX_FIFO_SIZE); result = vx185_snor_write_page(addr, chunksize, &written, mem); if(result != -EAGAIN){ total += written; }else{ result = 0; ++tries; } } if(tries >= 3){ result = -EIO; } vx185_snor_spi_up(); *retlen = total; return result; } static void vx185_snor_wait() { unsigned int status, port_cfg; while( (ioread32(registers_ptr + VX185_SPI0_STATUS_OFF) & SPI0_STATUS_BUSY) || (ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF) & SPI0_PORT_CONFIG_START)) { if(!panic_mode){ schedule(); } } } static int vx185_snor_wren(void) { u_int32_t reg; int rdsr, status; status = 0; vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); reg |= (SPI0_GLOB_CONFIG_CE | SPI0_GLOB_CONFIG_SPI_CLK_POL); reg &= ~SPI0_GLOB_CONFIG_PD; iowrite32(reg, registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); iowrite32((cmd_table->wren & SPI0_SPI_INSTR_MASK), registers_ptr + VX185_SPI0_SPI_INSTR_OFF); iowrite32(0, registers_ptr + VX185_SPI0_BLOCK_SIZE_OFF); iowrite32(0, registers_ptr + VX185_SPI0_SPI_ADDR_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~(SPI0_PORT_CONFIG_DEV_SEL_MASK | SPI0_PORT_CONFIG_IE); iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); iowrite32(0, registers_ptr + VX185_SPI0_STATUS_OFF); iowrite32(0, registers_ptr + VX185_SPI0_INT_STATUS_OFF); iowrite32(SPI0_INT_TRNF_ERR, registers_ptr + VX185_SPI0_INT_EN_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_START; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); status = vx185_snor_rdsr(&rdsr); if(status < 0 || !(rdsr & SPI0_DEV_RSR_WEL)){ pr_err("[%s] Setting WREN failed.\n", __func__); status = -EFAULT; } return status; } static unsigned int vx185_snor_read_id(void) { u_int32_t status, reg; vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); reg |= (SPI0_GLOB_CONFIG_CE | SPI0_GLOB_CONFIG_SPI_CLK_POL); reg &= ~SPI0_GLOB_CONFIG_PD; iowrite32(reg, registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); iowrite32(0x9f, registers_ptr + VX185_SPI0_SPI_INSTR_OFF); iowrite32(3, registers_ptr + VX185_SPI0_BLOCK_SIZE_OFF); iowrite32(0, registers_ptr + VX185_SPI0_SPI_ADDR_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~(SPI0_PORT_CONFIG_DEV_SEL_MASK | SPI0_PORT_CONFIG_IE); iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); iowrite32(0, registers_ptr + VX185_SPI0_STATUS_OFF); iowrite32(0, registers_ptr + VX185_SPI0_INT_STATUS_OFF); iowrite32(SPI0_INT_TRNF_ERR, registers_ptr + VX185_SPI0_INT_EN_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_START; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); vx185_snor_wait(); status = ioread32(rx_ptr); return status; } static int vx185_snor_sector_erase(u_int32_t addr, unsigned char cmd) { int status = 0; u_int32_t reg, rdsr; if((status = vx185_snor_wren()) != 0){ return status; } vx185_snor_wait(); reg = ioread32(registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); reg |= (SPI0_GLOB_CONFIG_CE | SPI0_GLOB_CONFIG_SPI_CLK_POL); reg &= ~SPI0_GLOB_CONFIG_PD; iowrite32(reg, registers_ptr + VX185_SPI0_GLOB_CONFIG_OFF); iowrite32((cmd & SPI0_SPI_INSTR_MASK), registers_ptr + VX185_SPI0_SPI_INSTR_OFF); iowrite32(1, registers_ptr + VX185_SPI0_BLOCK_SIZE_OFF); iowrite32((addr & SPI0_SPI_ADDR_MASK), registers_ptr + VX185_SPI0_SPI_ADDR_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg &= ~(SPI0_PORT_CONFIG_DEV_SEL_MASK | SPI0_PORT_CONFIG_IE); iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); iowrite32(0, registers_ptr + VX185_SPI0_STATUS_OFF); iowrite32(0, registers_ptr + VX185_SPI0_INT_STATUS_OFF); iowrite32(SPI0_INT_TRNF_ERR, registers_ptr + VX185_SPI0_INT_EN_OFF); reg = ioread32(registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); reg |= SPI0_PORT_CONFIG_START; iowrite32(reg, registers_ptr + VX185_SPI0_PORT_CONFIG_OFF); if(ioread32(registers_ptr + VX185_SPI0_INT_STATUS_OFF) & SPI0_INT_TRNF_ERR){ pr_err("[%s] Illegal SPI instruction.\n", __func__); status = -EFAULT; } vx185_snor_wait(); do{ msleep(1); status = vx185_snor_rdsr(&rdsr); }while(status == 0 && (rdsr & SPI0_DEV_RSR_WIP)); if(status < 0 || (rdsr & SPI0_DEV_RSR_E_RR)){ pr_err("[%s] Erasing sector %#x failed!\n", __func__, addr); status = -EFAULT; } return status; } static int __init vx185_snor_init (void); static void __exit vx185_snor_exit(void); static int __devinit vx185_snor_probe(struct platform_device *pdev); static int __devexit vx185_snor_remove(struct platform_device *pdev); /* driver device registration */ static struct platform_driver vx185_snor_driver = { .probe = vx185_snor_probe, .remove = __devexit_p(vx185_snor_remove), .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, }, }; static int __init vx185_snor_init (void) { u_int32_t gpio_func; pr_info("%s, Version %s (c) 2012 AVM GmbH.\n", DRIVER_DESC, DRIVER_VERSION); spi_registers = request_mem_region(VX185_SPI0_BASE, sizeof(struct __vx185_spi0_regs), "vx185_snor_io"); if(spi_registers == NULL){ pr_err("[%s] request_mem_region for spi_rx_fifo failed!\n", __func__); return -EFAULT; } registers_ptr = ioremap_nocache(spi_registers->start, (spi_registers->end - spi_registers->start + 1)); if(registers_ptr == NULL){ pr_err("[%s] ioremap_nocache for registers_ptr failed!\n", __func__); release_mem_region(VX185_SPI0_BASE, sizeof(struct __vx185_spi0_regs)); return -EFAULT; } spi_rx_fifo = request_mem_region(VX185_SPI0_RX_FIFO, sizeof(u_int32_t), "vx185_snor_rx"); if(spi_rx_fifo == NULL){ pr_err("[%s] request_mem_region for spi_rx_fifo failed!\n", __func__); iounmap(registers_ptr); release_mem_region(VX185_SPI0_BASE, sizeof(struct __vx185_spi0_regs)); return -EFAULT; } rx_ptr = ioremap_nocache(spi_rx_fifo->start, sizeof(u_int32_t)); if(rx_ptr == NULL){ pr_err("[%s] ioremap_nocache for rx_ptr failed!\n", __func__); iounmap(registers_ptr); release_mem_region(VX185_SPI0_BASE, sizeof(struct __vx185_spi0_regs)); release_mem_region(VX185_SPI0_RX_FIFO, sizeof(u_int32_t)); return -EFAULT; } spi_tx_fifo = request_mem_region(VX185_SPI0_TX_FIFO, sizeof(u_int32_t), "vx185_snor_tx"); if(spi_tx_fifo == NULL){ pr_err("[%s] request_mem_region for spi_tx_fifo failed!\n", __func__); iounmap(registers_ptr); release_mem_region(VX185_SPI0_BASE, sizeof(struct __vx185_spi0_regs)); iounmap(rx_ptr); release_mem_region(VX185_SPI0_RX_FIFO, sizeof(u_int32_t)); return -EFAULT; } tx_ptr = ioremap_nocache(spi_tx_fifo->start, sizeof(u_int32_t)); if(tx_ptr == NULL){ pr_err("[%s] ioremap_nocache for tx_ptr failed!\n", __func__); iounmap(registers_ptr); release_mem_region(VX185_SPI0_BASE, sizeof(struct __vx185_spi0_regs)); iounmap(rx_ptr); release_mem_region(VX185_SPI0_RX_FIFO, sizeof(u_int32_t)); release_mem_region(VX185_SPI0_TX_FIFO, sizeof(u_int32_t)); return -EFAULT; } pr_err("[%s] regs: %p rx_ptr: %p tx_ptr: %p\n", __func__, registers_ptr, rx_ptr, tx_ptr); #if 0 gpio_func = IKS_REG_R32(VX185_GPIO_MODE1); gpio_func &= ~((VX185_GPIO_MODE_OS << 0) | (VX185_GPIO_MODE_OS << 2) | (VX185_GPIO_MODE_OS << 4)); gpio_func |= (VX185_GPIO_MODE_OD << 0) | (VX185_GPIO_MODE_OD << 2)| (VX185_GPIO_MODE_OD << 4); IKS_REG_W32(gpio_func, VX185_GPIO_MODE1); gpio_func = IKS_REG_R32(VX185_GPIO_ALTFUNC_VAL); gpio_func |= 1 << 3; IKS_REG_W32(gpio_func, VX185_GPIO_ALTFUNC_VAL); gpio_func = IKS_REG_R32(VX185_GPIO_ALTFUNC_SEL); gpio_func |= (VX185_GPIO_ALTFUNC_SFLASH_CS | VX185_GPIO_ALTFUNC_SFLASH_HOLD | VX185_GPIO_ALTFUNC_SFLASH_WP); IKS_REG_W32(gpio_func, VX185_GPIO_ALTFUNC_SEL); #endif return platform_driver_register(&vx185_snor_driver); } static void __exit vx185_snor_exit(void) { platform_driver_unregister(&vx185_snor_driver); } static int __devexit vx185_snor_remove(struct platform_device *pdev __attribute__ ((unused))) { /* * nothing to do */ return 0; } static int __devinit vx185_snor_probe(struct platform_device *pdev) { struct vx185_snor_platform *plat; struct mtd_info *mtd = NULL; int status = 0; unsigned int flash_id, manufact_id, device_id, device_size; flash_id = vx185_snor_read_id(); pr_err("[%s] Chip-ID: %08x\n", __func__, flash_id); manufact_id = (flash_id >> 16) & 0xff; device_id = (flash_id >> 8) & 0xff; device_size = flash_id & 0xff; switch(manufact_id){ case MANUFACT_ID_SPANSION: if(flash_id == 0x00010215){ pr_err("[%s] found 4MB Spansion flash.\n", __func__); } else { pr_err("[%s] found unknown Spansion flash, assuming 4MB.\n", __func__); } layout = &layout_spansion; cmd_table = &spansion_cmd_table; break; case MANUFACT_ID_MACRONIX: pr_err("[%s] found Macronix flash.\n", __func__); layout = &layout_macronix; layout->size = 1 << device_size; cmd_table = ¯onix_cmd_table; break; default: pr_err("[%s] unknown flash found, assuming 256k.\n", __func__); layout = &layout_spansion; layout->size = 256 << 10; cmd_table = &spansion_cmd_table; break; } plat = (struct vx185_snor_platform *) pdev->dev.platform_data; mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); mtd->name = pdev->name; mtd->type = MTD_NORFLASH; mtd->flags = MTD_WRITEABLE; mtd->owner = THIS_MODULE; mtd->numeraseregions = 0; mtd->eraseregions = NULL; mtd->size = layout->size; mtd->writesize = 1; mtd->read = vx185_snor_read; mtd->write = vx185_snor_write; mtd->erase = vx185_snor_erase; mtd->erasesize = layout->sector_size; mtd->writesize = 1; if (plat != NULL && plat->nr_partitions > 0) { status = add_mtd_partitions(mtd, plat->partitions, plat->nr_partitions); } else { status = add_mtd_device(mtd); } if(status != 0){ kfree(mtd); status = -ENODEV; } return status; } int vx185_snor_panic_reinit(void) { int result = 0; if(down_trylock(&vx185_snor_sem)){ result = -EBUSY; } else { panic_mode = 1; } return result; } EXPORT_SYMBOL(vx185_snor_panic_reinit); module_init(vx185_snor_init); module_exit(vx185_snor_exit); MODULE_AUTHOR("Tido Klaassen "); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_LICENSE("GPL");