/* * This file contains glue for Atheros ath spi flash interface * Primitives are ath_spi_* * mtd flash implements are ath_flash_* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ath_flash.h" #include "../mtdcore.h" #include "../../char/tffs/linux_tffs.h" #define MTD_AVM_PARTITION_PARSER /* * statics */ static void ath_spi_write_enable(void); static void ath_spi_poll(unsigned int locked); static int ath_flash_probe(struct platform_device* pdev); static int ath_flash_remove(struct platform_device* pdev); #if !defined(ATH_SST_FLASH) static void ath_spi_write_page(uint32_t addr, uint8_t * data, int len); static void ath_spi_write_page_4b(uint32_t addr, uint8_t * data, int len); #endif static void ath_spi_sector_erase(uint32_t addr); static void ath_spi_sector_erase_4b(uint32_t addr); static bool disabled_mm_spi = false; void ath_spi_disable_mm(void) { ath_reg_wr_nf(ATH_SPI_FS, 1); disabled_mm_spi = true; } static inline void ath_spi_enable_soft_access(void) { if (disabled_mm_spi) { return; } ath_reg_wr_nf(ATH_SPI_FS, 1); } static inline void ath_spi_disable_soft_access(void) { if (disabled_mm_spi) { return; } do { ath_reg_wr(ATH_SPI_FS, 0); } while(ath_reg_rd(ATH_SPI_FS) == 1); } static const char *part_probes[] __initdata = { "avm_jffs2", "avm_squashfs", "cmdlinepart", "RedBoot", NULL }; #define MTD_FLAG_4_BYTE_ADDRESSING (1 << 30) #define ATH_FLASH_SIZE_2MB ( 2*1024*1024) #define ATH_FLASH_SIZE_4MB ( 4*1024*1024) #define ATH_FLASH_SIZE_8MB ( 8*1024*1024) #define ATH_FLASH_SIZE_16MB (16*1024*1024) #define ATH_FLASH_SECTOR_SIZE_64KB (64*1024) #define ATH_FLASH_PG_SIZE_256B 256 #define ATH_FLASH_NAME "ath-nor" #define ATH_SPI_DATA_CTRL_EN (1 << 31) #define ATH_SPI_DATA_CTRL_CS0 (1 << 28) #define ATH_SPI_DATA_CTRL_TERM (1 << 26) #define ATH_SPI_DATA_CTRL_BITS(x) (x) static struct platform_driver ath_flash_driver = { .probe = ath_flash_probe, .remove = ath_flash_remove, .driver = { .name = ATH_FLASH_NAME, }, }; /*------------------------------------------------------------------------------------------*\ * bank geometry \*------------------------------------------------------------------------------------------*/ typedef struct ath_flash_geom { uint32_t sector_size; uint32_t nsectors; uint32_t pgsize; } ath_flash_geom_t; /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ ath_flash_geom_t flash_geom_tbl[ATH_FLASH_MAX_BANKS] = { { .sector_size = ATH_FLASH_SECTOR_SIZE_64KB, .pgsize = ATH_FLASH_PG_SIZE_256B } }; static DEFINE_SEMAPHORE(ath_flash_sem); /*------------------------------------------------------------------------------------------*\ * Locking API \*------------------------------------------------------------------------------------------*/ void ath_flash_spi_down(void) { down(&ath_flash_sem); } void ath_flash_spi_up(void) { up(&ath_flash_sem); } EXPORT_SYMBOL(ath_flash_spi_down); EXPORT_SYMBOL(ath_flash_spi_up); #if defined(ATH_SST_FLASH) void ath_spi_flash_unblock(void) { ath_spi_write_enable(); ath_spi_bit_banger(ATH_SPI_CMD_WRITE_SR); ath_spi_bit_banger(0x0); ath_spi_go(); ath_spi_poll(0); } #endif static void ath_flash_spi_busy_wait(void) { while (ath_reg_rd(ATH_SPI_DATA_CTRL) & ATH_SPI_DATA_CTRL_EN) ; } static uint8_t ath_spi_reg_rd(uint8_t reg) { // Send command ath_reg_wr_nf(ATH_SPI_DATA_OUT, reg); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); // Read ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); return ath_reg_rd(ATH_SPI_DATA_IN) & 0xFF; } static void ath_spi_poll2(void) { uint8_t status; do { status = ath_spi_reg_rd(ATH_SPI_CMD_RD_STATUS); } while(status & 0x1); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int ath_flash_erase(struct mtd_info *mtd, struct erase_info *instr) { int nsect, s_curr, s_last; uint64_t res; if (instr->addr + instr->len > mtd->size) return (-EINVAL); ath_flash_spi_down(); res = instr->len; do_div(res, mtd->erasesize); nsect = res; if (((uint32_t)instr->len) % mtd->erasesize) nsect ++; res = instr->addr; do_div(res,mtd->erasesize); s_curr = res; s_last = s_curr + nsect; do { if (mtd->flags & MTD_FLAG_4_BYTE_ADDRESSING) { ath_spi_sector_erase_4b(s_curr * ATH_SPI_SECTOR_SIZE); } else { ath_spi_sector_erase(s_curr * ATH_SPI_SECTOR_SIZE); } } while (++s_curr < s_last); ath_spi_done(); ath_flash_spi_up(); if (instr->callback) { instr->state |= MTD_ERASE_DONE; instr->callback(instr); } #if defined(FLASH_ACCESS_STATISTIC) display_periodical_statistic(); #endif/*--- #if defined(FLASH_ACCESS_STATISTIC) ---*/ return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ struct _nmi_vector_gap { unsigned int in_use; unsigned long long start; unsigned long long gap_size; unsigned long long end; }; struct _nmi_vector_gap nmi_vector_gap; void set_nmi_vetor_gap(unsigned int start, unsigned int firmware_size, unsigned int gap_size) { nmi_vector_gap.start = (unsigned long long)start & ((16ULL << 20) - 1ULL); /*--- funktioniert bis zur Flashgröße von 16 MByte ---*/ nmi_vector_gap.gap_size = gap_size; nmi_vector_gap.end = (unsigned long long)firmware_size; nmi_vector_gap.in_use = 1; pr_info("[ath_flash] NMI GAP start=0x%08llx size=0x%08llx end=0x%08llx\n", nmi_vector_gap.start, nmi_vector_gap.gap_size, nmi_vector_gap.end); } static int ath_flash_read_spi(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { u32 temp; size_t orig_len = len; ath_flash_spi_down(); // Send command ath_reg_wr_nf(ATH_SPI_DATA_OUT, ATH_SPI_CMD_FAST_READ_4B); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); // Send address ath_reg_wr_nf(ATH_SPI_DATA_OUT, from); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(32)); ath_flash_spi_busy_wait(); // Read a dummy byte ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); for (; len > 4; len -= 4, buf += 4) { // Read data ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(32)); ath_flash_spi_busy_wait(); *((u32*)buf) = ath_reg_rd(ATH_SPI_DATA_IN); } // Read odd ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(8 * len)); ath_flash_spi_busy_wait(); temp = ath_reg_rd(ATH_SPI_DATA_IN); for (; len > 0; len--, buf++) { *buf = (temp >> (8 * (len - 1))) & 0xFF; } ath_flash_spi_up(); *retlen = orig_len; return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void local_memcpy(uint8_t *to, uint8_t *from, size_t len) { uint32_t addr = (uint32_t)from | 0xbf000000; flush_icache_range((uint32_t)to, (uint32_t)to + len); memcpy(to, (void *)addr, len); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int ath_flash_read_mm(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { ath_flash_spi_down(); local_memcpy(buf, (uint8_t*)(uintptr_t)from, len); *retlen = len; ath_flash_spi_up(); #if defined(FLASH_ACCESS_STATISTIC) /*--- generic_stat(&read_stat, get_cycles() - cycles); ---*/ #endif/*--- #if defined(FLASH_ACCESS_STATISTIC) ---*/ return 0; } static int ath_flash_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { size_t ret1 = 0; size_t ret2 = 0; int ret; int (*read)(struct mtd_info*, loff_t, size_t, size_t*, u_char*); // No data to read, skip reading if (len == 0) { *retlen = 0; return 0; } if (from + len > mtd->size) { pr_err("[ath_flash] Out of bounds read. 0x%08llx + 0x%08x > 0x%08llx\n", from, len, mtd->size); return -EINVAL; } if (mtd->flags & MTD_FLAG_4_BYTE_ADDRESSING) { read = ath_flash_read_spi; } else { read = ath_flash_read_mm; } // Not in use, short cut if (!nmi_vector_gap.in_use) { return read(mtd, from, len, retlen, buf); } // Read up to gap if (from < nmi_vector_gap.start) { ret = read(mtd, from, min(len, (size_t)(nmi_vector_gap.start - from)), &ret1, buf); if (ret < 0) { return ret; } // Advance buffer buf += ret1; // Adjust new reading locations from += ret1; len -= ret1; } // Read after gap if (from >= nmi_vector_gap.start && len > 0) { if (from < nmi_vector_gap.end) { // Adjust for the gap size skipped from += nmi_vector_gap.gap_size; } ret = read(mtd, from, len, &ret2, buf); if (ret < 0) { return ret; } } *retlen = ret1 + ret2; return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ #if defined(ATH_SST_FLASH) #error Need to adapt this code to 4 byte addressing static int ath_flash_write(struct mtd_info *mtd, loff_t dst, size_t len, size_t * retlen, const u_char * src) { uint32_t val; //printk("write len: %lu dst: 0x%x src: %p\n", len, dst, src); *retlen = len; for (; len; len--, dst++, src++) { ath_spi_write_enable(); // dont move this above 'for' ath_spi_bit_banger(ATH_SPI_CMD_PAGE_PROG); ath_spi_send_addr(dst); val = *src & 0xff; ath_spi_bit_banger(val); ath_spi_go(); ath_spi_poll(0); } /* * Disable the Function Select * Without this we can't re-read the written data */ ath_spi_disable_soft_access(); if (len) { *retlen -= len; return -EIO; } return 0; } #else static int ath_flash_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { int total = 0, len_this_lp, bytes_this_page; uint32_t addr = 0; u_char *mem; ath_flash_spi_down(); /*--- printk("spi: SPI_CONTROL %08x: %08x divider=%d+1\n", ATH_SPI_CLOCK, ath_reg_rd(ATH_SPI_CLOCK), (ath_reg_rd(ATH_SPI_CLOCK) & ~(0x1 << 5))); ---*/ while (total < len) { mem = (u_char *) (buf + total); addr = to + total; bytes_this_page = ATH_SPI_PAGE_SIZE - (addr % ATH_SPI_PAGE_SIZE); len_this_lp = min(((int)len - total), bytes_this_page); if (mtd->flags & MTD_FLAG_4_BYTE_ADDRESSING) { ath_spi_write_page_4b(addr, mem, len_this_lp); } else { ath_spi_write_page(addr, mem, len_this_lp); } total += len_this_lp; } ath_spi_done(); ath_flash_spi_up(); #if defined(FLASH_ACCESS_STATISTIC) display_periodical_statistic(); #endif/*--- #if defined(FLASH_ACCESS_STATISTIC) ---*/ *retlen = len; return 0; } #endif /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static DEFINE_SPINLOCK(panic_locking); static inline void panic_lock(unsigned long *flags) { spin_lock_irqsave(&panic_locking, *flags); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline void panic_unlock(unsigned long flags) { spin_unlock_irqrestore(&panic_locking, flags); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static struct mtd_info *panic_reinit(struct mtd_info *mtd) { unsigned long flags; pr_err("[%s] Called for mtd %s\n", __func__, mtd->name); panic_lock(&flags); /* * We may enter here when the kernel is already writing via SPI and thus has * the ath_flash_sem aquired. We need to break this lock in order to let the * panic log writing succeed later. * * First we try to aquire the lock, if this succeeds everything is okay. * If not another place is currently writing. Therefore we break the lock by * faking a up and then take the down, which will now succeed. */ if(down_trylock(&ath_flash_sem)){ pr_err("[%s] mtd %s ath_flash_sem locked\n", __func__, mtd->name); ath_flash_spi_up(); ath_flash_spi_down(); } if (mtd->flags & MTD_FLAG_4_BYTE_ADDRESSING) { ath_reg_wr_nf(ATH_SPI_DATA_OUT, ATH_SPI_CMD_WRDI); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); ath_spi_poll2(); } else { ath_spi_enable_soft_access(); ath_reg_wr_nf(ATH_SPI_WRITE, ATH_SPI_CS_DIS); ath_spi_bit_banger(ATH_SPI_CMD_WRDI); ath_spi_go(); ath_spi_poll(1); ath_spi_disable_soft_access(); } ath_flash_spi_up(); panic_unlock(flags); return mtd; } /*------------------------------------------------------------------------------------------*\ * sets up flash_info and returns size of FLASH (bytes) \*------------------------------------------------------------------------------------------*/ static int ath_flash_probe(struct platform_device* pdev) { struct mtd_partition *mtd_parts = NULL; /*--- part-pointer wird von jffs2-parser gefuellt ---*/ int np; ath_flash_geom_t *geom; struct mtd_info *mtd; uint8_t index = 0; sema_init(&ath_flash_sem, 1); #if defined(ATH_SST_FLASH) ath_reg_wr_nf(ATH_SPI_CLOCK, 0x3); ath_spi_flash_unblock(); ath_spi_disable_soft_access(); #else #if !defined CONFIG_SOC_AR934X && !defined CONFIG_SOC_QCA953X ath_reg_wr_nf(ATH_SPI_CLOCK, 0x43); #endif #endif geom = &flash_geom_tbl[index]; mtd = kmalloc(sizeof(struct mtd_info), GFP_KERNEL); if (!mtd) { printk("Cant allocate mtd stuff\n"); return -1; } memset(mtd, 0, sizeof(struct mtd_info)); mtd->name = ATH_FLASH_NAME; mtd->type = MTD_NORFLASH; mtd->flags = MTD_CAP_NORFLASH | MTD_WRITEABLE; mtd->size = pdev->resource[0].end - pdev->resource[0].start; mtd->erasesize = geom->sector_size; mtd->numeraseregions = 0; mtd->eraseregions = NULL; mtd->owner = THIS_MODULE; mtd->_erase = ath_flash_erase; mtd->_read = ath_flash_read; mtd->_write = ath_flash_write; mtd->writesize = 1; if (mtd->size > 16 * 1024 * 1024) { pr_info("[ath_flash] Use 4 byte addressing\n"); mtd->flags |= MTD_FLAG_4_BYTE_ADDRESSING; // 4 byte addressing does not use memory mapped io ath_spi_disable_mm(); } np = parse_mtd_partitions(mtd, part_probes, &mtd_parts, 0); // AVM #if defined(CONFIG_TFFS_DEV_LEGACY) TFFS3_Register_Panic_CB(mtd, panic_reinit); #endif #ifdef MTD_AVM_PARTITION_PARSER if (np > 0) { /*-----------------------------------------------------------------*\ * Partitionenen gefunden, add_mtd_partions traegt diese als * mtds in 'mtd_table' ein \*-----------------------------------------------------------------*/ add_mtd_partitions(mtd, mtd_parts, np); return 0; } else { printk("[%s] No partitions flash found\n", __func__); } #endif /*-----------------------------------------------------------------*\ * keine Partitionenen gefunen, add_mtd_device * traegt das gesamte mtd 'mtd_table' ein \*-----------------------------------------------------------------*/ add_mtd_device(mtd); return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int ath_flash_remove(struct platform_device* pdev __attribute__((unused))) { /* * nothing to do */ return 0; } /*------------------------------------------------------------------------------------------*\ * Primitives to implement flash operations \*------------------------------------------------------------------------------------------*/ static void ath_spi_write_enable() { ath_spi_enable_soft_access(); ath_reg_wr_nf(ATH_SPI_WRITE, ATH_SPI_CS_DIS); ath_spi_bit_banger(ATH_SPI_CMD_WREN); ath_spi_go(); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void ath_spi_poll(unsigned int locked) { int rd; /*--------------------------------------------------------------------------------*\ * ausgemessen: 5 Durchlaeufe pro 10 usec - es werden mindestens 500 us benoetigt \*--------------------------------------------------------------------------------*/ do { ath_reg_wr_nf(ATH_SPI_WRITE, ATH_SPI_CS_DIS); ath_spi_bit_banger(ATH_SPI_CMD_RD_STATUS); ath_spi_delay_8(); rd = (ath_reg_rd(ATH_SPI_RD_STATUS) & 1); } while (rd); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void ath_spi_write_page(uint32_t addr, uint8_t *data, int len) { int i; uint8_t ch; ath_spi_write_enable(); ath_spi_bit_banger(ATH_SPI_CMD_PAGE_PROG); ath_spi_send_addr(addr); for (i = 0; i < len; i++) { ch = *(data + i); ath_spi_bit_banger(ch); } ath_spi_go(); ath_spi_poll(1); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void ath_spi_write_page_4b(uint32_t addr, uint8_t *data, int len) { // Send Write Enable ath_reg_wr_nf(ATH_SPI_DATA_OUT, ATH_SPI_CMD_WREN); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); // Send command ath_reg_wr_nf(ATH_SPI_DATA_OUT, ATH_SPI_CMD_PAGE_PROG_4B); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); // Send address ath_reg_wr_nf(ATH_SPI_DATA_OUT, addr); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(32)); ath_flash_spi_busy_wait(); // Write all bytes but the last for (; len > 1; len--, data++) { ath_reg_wr_nf(ATH_SPI_DATA_OUT, *data); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); } // Write last byte with disabling CS ath_reg_wr_nf(ATH_SPI_DATA_OUT, *data); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); ath_spi_poll2(); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void ath_spi_sector_erase(uint32_t addr) { ath_spi_write_enable(); ath_spi_bit_banger(ATH_SPI_CMD_SECTOR_ERASE); ath_spi_send_addr(addr); ath_spi_go(); #if 0 /* * Do not touch the GPIO's unnecessarily. Might conflict * with customer's settings. */ display(0x7d); #endif ath_spi_poll(0); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void ath_spi_sector_erase_4b(uint32_t addr) { // Send Write Enable ath_reg_wr_nf(ATH_SPI_DATA_OUT, ATH_SPI_CMD_WREN); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); // Send command ath_reg_wr_nf(ATH_SPI_DATA_OUT, ATH_SPI_CMD_SECTOR_ERASE_4B); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_BITS(8)); ath_flash_spi_busy_wait(); // Send address ath_reg_wr_nf(ATH_SPI_DATA_OUT, addr); ath_reg_wr_nf(ATH_SPI_DATA_CTRL, ATH_SPI_DATA_CTRL_EN | ATH_SPI_DATA_CTRL_CS0 | ATH_SPI_DATA_CTRL_TERM | ATH_SPI_DATA_CTRL_BITS(32)); ath_flash_spi_busy_wait(); ath_spi_poll2(); } /*------------------------------------------------------------------------------------------*\ * Module Functions \*------------------------------------------------------------------------------------------*/ static int __init ath_flash_init (void) { printk(KERN_INFO "Registering ATH-flash-driver...\n"); #if defined(FLASH_ACCESS_STATISTIC) init_generic_stat(&erase_stat); init_generic_stat(&write_stat); init_generic_stat(&read_stat); #endif/*--- #if defined(FLASH_ACCESS_STATISTIC) ---*/ return platform_driver_register(&ath_flash_driver); } static void __exit ath_flash_exit(void) { platform_driver_unregister(&ath_flash_driver); } module_init(ath_flash_init); module_exit(ath_flash_exit);