| /* | |
| * MTD chip driver for pre-CFI Sharp flash chips | |
| * | |
| * Copyright 2000,2001 David A. Schleef <ds@schleef.org> | |
| * 2000,2001 Lineo, Inc. | |
| * | |
| * $Id: sharp.c,v 1.17 2005/11/29 14:28:28 gleixner Exp $ | |
| * | |
| * Devices supported: | |
| * LH28F016SCT Symmetrical block flash memory, 2Mx8 | |
| * LH28F008SCT Symmetrical block flash memory, 1Mx8 | |
| * | |
| * Documentation: | |
| * http://www.sharpmeg.com/datasheets/memic/flashcmp/ | |
| * http://www.sharpmeg.com/datasheets/memic/flashcmp/01symf/16m/016sctl9.pdf | |
| * 016sctl9.pdf | |
| * | |
| * Limitations: | |
| * This driver only supports 4x1 arrangement of chips. | |
| * Not tested on anything but PowerPC. | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/types.h> | |
| #include <linux/sched.h> | |
| #include <linux/errno.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/mtd/map.h> | |
| #include <linux/mtd/mtd.h> | |
| #include <linux/mtd/cfi.h> | |
| #include <linux/delay.h> | |
| #include <linux/init.h> | |
| #include <linux/slab.h> | |
| #define CMD_RESET 0xffffffff | |
| #define CMD_READ_ID 0x90909090 | |
| #define CMD_READ_STATUS 0x70707070 | |
| #define CMD_CLEAR_STATUS 0x50505050 | |
| #define CMD_BLOCK_ERASE_1 0x20202020 | |
| #define CMD_BLOCK_ERASE_2 0xd0d0d0d0 | |
| #define CMD_BYTE_WRITE 0x40404040 | |
| #define CMD_SUSPEND 0xb0b0b0b0 | |
| #define CMD_RESUME 0xd0d0d0d0 | |
| #define CMD_SET_BLOCK_LOCK_1 0x60606060 | |
| #define CMD_SET_BLOCK_LOCK_2 0x01010101 | |
| #define CMD_SET_MASTER_LOCK_1 0x60606060 | |
| #define CMD_SET_MASTER_LOCK_2 0xf1f1f1f1 | |
| #define CMD_CLEAR_BLOCK_LOCKS_1 0x60606060 | |
| #define CMD_CLEAR_BLOCK_LOCKS_2 0xd0d0d0d0 | |
| #define SR_READY 0x80808080 // 1 = ready | |
| #define SR_ERASE_SUSPEND 0x40404040 // 1 = block erase suspended | |
| #define SR_ERROR_ERASE 0x20202020 // 1 = error in block erase or clear lock bits | |
| #define SR_ERROR_WRITE 0x10101010 // 1 = error in byte write or set lock bit | |
| #define SR_VPP 0x08080808 // 1 = Vpp is low | |
| #define SR_WRITE_SUSPEND 0x04040404 // 1 = byte write suspended | |
| #define SR_PROTECT 0x02020202 // 1 = lock bit set | |
| #define SR_RESERVED 0x01010101 | |
| #define SR_ERRORS (SR_ERROR_ERASE|SR_ERROR_WRITE|SR_VPP|SR_PROTECT) | |
| /* Configuration options */ | |
| #undef AUTOUNLOCK /* automatically unlocks blocks before erasing */ | |
| static struct mtd_info *sharp_probe(struct map_info *); | |
| static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd); | |
| static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len, | |
| size_t *retlen, u_char *buf); | |
| static int sharp_write(struct mtd_info *mtd, loff_t from, size_t len, | |
| size_t *retlen, const u_char *buf); | |
| static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr); | |
| static void sharp_sync(struct mtd_info *mtd); | |
| static int sharp_suspend(struct mtd_info *mtd); | |
| static void sharp_resume(struct mtd_info *mtd); | |
| static void sharp_destroy(struct mtd_info *mtd); | |
| static int sharp_write_oneword(struct map_info *map, struct flchip *chip, | |
| unsigned long adr, __u32 datum); | |
| static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip, | |
| unsigned long adr); | |
| #ifdef AUTOUNLOCK | |
| static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip, | |
| unsigned long adr); | |
| #endif | |
| struct sharp_info{ | |
| struct flchip *chip; | |
| int bogus; | |
| int chipshift; | |
| int numchips; | |
| struct flchip chips[1]; | |
| }; | |
| static void sharp_destroy(struct mtd_info *mtd); | |
| static struct mtd_chip_driver sharp_chipdrv = { | |
| .probe = sharp_probe, | |
| .destroy = sharp_destroy, | |
| .name = "sharp", | |
| .module = THIS_MODULE | |
| }; | |
| static struct mtd_info *sharp_probe(struct map_info *map) | |
| { | |
| struct mtd_info *mtd = NULL; | |
| struct sharp_info *sharp = NULL; | |
| int width; | |
| mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); | |
| if(!mtd) | |
| return NULL; | |
| sharp = kmalloc(sizeof(*sharp), GFP_KERNEL); | |
| if(!sharp) { | |
| kfree(mtd); | |
| return NULL; | |
| } | |
| memset(mtd, 0, sizeof(*mtd)); | |
| width = sharp_probe_map(map,mtd); | |
| if(!width){ | |
| kfree(mtd); | |
| kfree(sharp); | |
| return NULL; | |
| } | |
| mtd->priv = map; | |
| mtd->type = MTD_NORFLASH; | |
| mtd->erase = sharp_erase; | |
| mtd->read = sharp_read; | |
| mtd->write = sharp_write; | |
| mtd->sync = sharp_sync; | |
| mtd->suspend = sharp_suspend; | |
| mtd->resume = sharp_resume; | |
| mtd->flags = MTD_CAP_NORFLASH; | |
| mtd->writesize = 1; | |
| mtd->name = map->name; | |
| memset(sharp, 0, sizeof(*sharp)); | |
| sharp->chipshift = 23; | |
| sharp->numchips = 1; | |
| sharp->chips[0].start = 0; | |
| sharp->chips[0].state = FL_READY; | |
| sharp->chips[0].mutex = &sharp->chips[0]._spinlock; | |
| sharp->chips[0].word_write_time = 0; | |
| init_waitqueue_head(&sharp->chips[0].wq); | |
| spin_lock_init(&sharp->chips[0]._spinlock); | |
| map->fldrv = &sharp_chipdrv; | |
| map->fldrv_priv = sharp; | |
| __module_get(THIS_MODULE); | |
| return mtd; | |
| } | |
| static inline void sharp_send_cmd(struct map_info *map, unsigned long cmd, unsigned long adr) | |
| { | |
| map_word map_cmd; | |
| map_cmd.x[0] = cmd; | |
| map_write(map, map_cmd, adr); | |
| } | |
| static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd) | |
| { | |
| map_word tmp, read0, read4; | |
| unsigned long base = 0; | |
| int width = 4; | |
| tmp = map_read(map, base+0); | |
| sharp_send_cmd(map, CMD_READ_ID, base+0); | |
| read0 = map_read(map, base+0); | |
| read4 = map_read(map, base+4); | |
| if(read0.x[0] == 0x89898989){ | |
| printk("Looks like sharp flash\n"); | |
| switch(read4.x[0]){ | |
| case 0xaaaaaaaa: | |
| case 0xa0a0a0a0: | |
| /* aa - LH28F016SCT-L95 2Mx8, 32 64k blocks*/ | |
| /* a0 - LH28F016SCT-Z4 2Mx8, 32 64k blocks*/ | |
| mtd->erasesize = 0x10000 * width; | |
| mtd->size = 0x200000 * width; | |
| return width; | |
| case 0xa6a6a6a6: | |
| /* a6 - LH28F008SCT-L12 1Mx8, 16 64k blocks*/ | |
| /* a6 - LH28F008SCR-L85 1Mx8, 16 64k blocks*/ | |
| mtd->erasesize = 0x10000 * width; | |
| mtd->size = 0x100000 * width; | |
| return width; | |
| #if 0 | |
| case 0x00000000: /* unknown */ | |
| /* XX - LH28F004SCT 512kx8, 8 64k blocks*/ | |
| mtd->erasesize = 0x10000 * width; | |
| mtd->size = 0x80000 * width; | |
| return width; | |
| #endif | |
| default: | |
| printk("Sort-of looks like sharp flash, 0x%08lx 0x%08lx\n", | |
| read0.x[0], read4.x[0]); | |
| } | |
| }else if((map_read(map, base+0).x[0] == CMD_READ_ID)){ | |
| /* RAM, probably */ | |
| printk("Looks like RAM\n"); | |
| map_write(map, tmp, base+0); | |
| }else{ | |
| printk("Doesn't look like sharp flash, 0x%08lx 0x%08lx\n", | |
| read0.x[0], read4.x[0]); | |
| } | |
| return 0; | |
| } | |
| /* This function returns with the chip->mutex lock held. */ | |
| static int sharp_wait(struct map_info *map, struct flchip *chip) | |
| { | |
| int i; | |
| map_word status; | |
| unsigned long timeo = jiffies + HZ; | |
| DECLARE_WAITQUEUE(wait, current); | |
| int adr = 0; | |
| retry: | |
| spin_lock_bh(chip->mutex); | |
| switch(chip->state){ | |
| case FL_READY: | |
| sharp_send_cmd(map, CMD_READ_STATUS, adr); | |
| chip->state = FL_STATUS; | |
| case FL_STATUS: | |
| for(i=0;i<100;i++){ | |
| status = map_read(map, adr); | |
| if((status.x[0] & SR_READY)==SR_READY) | |
| break; | |
| udelay(1); | |
| } | |
| break; | |
| default: | |
| printk("Waiting for chip\n"); | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| add_wait_queue(&chip->wq, &wait); | |
| spin_unlock_bh(chip->mutex); | |
| schedule(); | |
| remove_wait_queue(&chip->wq, &wait); | |
| if(signal_pending(current)) | |
| return -EINTR; | |
| timeo = jiffies + HZ; | |
| goto retry; | |
| } | |
| sharp_send_cmd(map, CMD_RESET, adr); | |
| chip->state = FL_READY; | |
| return 0; | |
| } | |
| static void sharp_release(struct flchip *chip) | |
| { | |
| wake_up(&chip->wq); | |
| spin_unlock_bh(chip->mutex); | |
| } | |
| static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len, | |
| size_t *retlen, u_char *buf) | |
| { | |
| struct map_info *map = mtd->priv; | |
| struct sharp_info *sharp = map->fldrv_priv; | |
| int chipnum; | |
| int ret = 0; | |
| int ofs = 0; | |
| chipnum = (from >> sharp->chipshift); | |
| ofs = from & ((1 << sharp->chipshift)-1); | |
| *retlen = 0; | |
| while(len){ | |
| unsigned long thislen; | |
| if(chipnum>=sharp->numchips) | |
| break; | |
| thislen = len; | |
| if(ofs+thislen >= (1<<sharp->chipshift)) | |
| thislen = (1<<sharp->chipshift) - ofs; | |
| ret = sharp_wait(map,&sharp->chips[chipnum]); | |
| if(ret<0) | |
| break; | |
| map_copy_from(map,buf,ofs,thislen); | |
| sharp_release(&sharp->chips[chipnum]); | |
| *retlen += thislen; | |
| len -= thislen; | |
| buf += thislen; | |
| ofs = 0; | |
| chipnum++; | |
| } | |
| return ret; | |
| } | |
| static int sharp_write(struct mtd_info *mtd, loff_t to, size_t len, | |
| size_t *retlen, const u_char *buf) | |
| { | |
| struct map_info *map = mtd->priv; | |
| struct sharp_info *sharp = map->fldrv_priv; | |
| int ret = 0; | |
| int i,j; | |
| int chipnum; | |
| unsigned long ofs; | |
| union { u32 l; unsigned char uc[4]; } tbuf; | |
| *retlen = 0; | |
| while(len){ | |
| tbuf.l = 0xffffffff; | |
| chipnum = to >> sharp->chipshift; | |
| ofs = to & ((1<<sharp->chipshift)-1); | |
| j=0; | |
| for(i=ofs&3;i<4 && len;i++){ | |
| tbuf.uc[i] = *buf; | |
| buf++; | |
| to++; | |
| len--; | |
| j++; | |
| } | |
| sharp_write_oneword(map, &sharp->chips[chipnum], ofs&~3, tbuf.l); | |
| if(ret<0) | |
| return ret; | |
| (*retlen)+=j; | |
| } | |
| return 0; | |
| } | |
| static int sharp_write_oneword(struct map_info *map, struct flchip *chip, | |
| unsigned long adr, __u32 datum) | |
| { | |
| int ret; | |
| int timeo; | |
| int try; | |
| int i; | |
| map_word data, status; | |
| status.x[0] = 0; | |
| ret = sharp_wait(map,chip); | |
| for(try=0;try<10;try++){ | |
| sharp_send_cmd(map, CMD_BYTE_WRITE, adr); | |
| /* cpu_to_le32 -> hack to fix the writel be->le conversion */ | |
| data.x[0] = cpu_to_le32(datum); | |
| map_write(map, data, adr); | |
| chip->state = FL_WRITING; | |
| timeo = jiffies + (HZ/2); | |
| sharp_send_cmd(map, CMD_READ_STATUS, adr); | |
| for(i=0;i<100;i++){ | |
| status = map_read(map, adr); | |
| if((status.x[0] & SR_READY) == SR_READY) | |
| break; | |
| } | |
| if(i==100){ | |
| printk("sharp: timed out writing\n"); | |
| } | |
| if(!(status.x[0] & SR_ERRORS)) | |
| break; | |
| printk("sharp: error writing byte at addr=%08lx status=%08lx\n", adr, status.x[0]); | |
| sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); | |
| } | |
| sharp_send_cmd(map, CMD_RESET, adr); | |
| chip->state = FL_READY; | |
| wake_up(&chip->wq); | |
| spin_unlock_bh(chip->mutex); | |
| return 0; | |
| } | |
| static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr) | |
| { | |
| struct map_info *map = mtd->priv; | |
| struct sharp_info *sharp = map->fldrv_priv; | |
| unsigned long adr,len; | |
| int chipnum, ret=0; | |
| //printk("sharp_erase()\n"); | |
| if(instr->addr & (mtd->erasesize - 1)) | |
| return -EINVAL; | |
| if(instr->len & (mtd->erasesize - 1)) | |
| return -EINVAL; | |
| if(instr->len + instr->addr > mtd->size) | |
| return -EINVAL; | |
| chipnum = instr->addr >> sharp->chipshift; | |
| adr = instr->addr & ((1<<sharp->chipshift)-1); | |
| len = instr->len; | |
| while(len){ | |
| ret = sharp_erase_oneblock(map, &sharp->chips[chipnum], adr); | |
| if(ret)return ret; | |
| adr += mtd->erasesize; | |
| len -= mtd->erasesize; | |
| if(adr >> sharp->chipshift){ | |
| adr = 0; | |
| chipnum++; | |
| if(chipnum>=sharp->numchips) | |
| break; | |
| } | |
| } | |
| instr->state = MTD_ERASE_DONE; | |
| mtd_erase_callback(instr); | |
| return 0; | |
| } | |
| static int sharp_do_wait_for_ready(struct map_info *map, struct flchip *chip, | |
| unsigned long adr) | |
| { | |
| int ret; | |
| unsigned long timeo; | |
| map_word status; | |
| DECLARE_WAITQUEUE(wait, current); | |
| sharp_send_cmd(map, CMD_READ_STATUS, adr); | |
| status = map_read(map, adr); | |
| timeo = jiffies + HZ; | |
| while(time_before(jiffies, timeo)){ | |
| sharp_send_cmd(map, CMD_READ_STATUS, adr); | |
| status = map_read(map, adr); | |
| if((status.x[0] & SR_READY)==SR_READY){ | |
| ret = 0; | |
| goto out; | |
| } | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| add_wait_queue(&chip->wq, &wait); | |
| //spin_unlock_bh(chip->mutex); | |
| schedule_timeout(1); | |
| schedule(); | |
| remove_wait_queue(&chip->wq, &wait); | |
| //spin_lock_bh(chip->mutex); | |
| if (signal_pending(current)){ | |
| ret = -EINTR; | |
| goto out; | |
| } | |
| } | |
| ret = -ETIME; | |
| out: | |
| return ret; | |
| } | |
| static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip, | |
| unsigned long adr) | |
| { | |
| int ret; | |
| //int timeo; | |
| map_word status; | |
| //int i; | |
| //printk("sharp_erase_oneblock()\n"); | |
| #ifdef AUTOUNLOCK | |
| /* This seems like a good place to do an unlock */ | |
| sharp_unlock_oneblock(map,chip,adr); | |
| #endif | |
| sharp_send_cmd(map, CMD_BLOCK_ERASE_1, adr); | |
| sharp_send_cmd(map, CMD_BLOCK_ERASE_2, adr); | |
| chip->state = FL_ERASING; | |
| ret = sharp_do_wait_for_ready(map,chip,adr); | |
| if(ret<0)return ret; | |
| sharp_send_cmd(map, CMD_READ_STATUS, adr); | |
| status = map_read(map, adr); | |
| if(!(status.x[0] & SR_ERRORS)){ | |
| sharp_send_cmd(map, CMD_RESET, adr); | |
| chip->state = FL_READY; | |
| //spin_unlock_bh(chip->mutex); | |
| return 0; | |
| } | |
| printk("sharp: error erasing block at addr=%08lx status=%08lx\n", adr, status.x[0]); | |
| sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); | |
| //spin_unlock_bh(chip->mutex); | |
| return -EIO; | |
| } | |
| #ifdef AUTOUNLOCK | |
| static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip, | |
| unsigned long adr) | |
| { | |
| int i; | |
| map_word status; | |
| sharp_send_cmd(map, CMD_CLEAR_BLOCK_LOCKS_1, adr); | |
| sharp_send_cmd(map, CMD_CLEAR_BLOCK_LOCKS_2, adr); | |
| udelay(100); | |
| status = map_read(map, adr); | |
| printk("status=%08lx\n", status.x[0]); | |
| for(i=0;i<1000;i++){ | |
| //sharp_send_cmd(map, CMD_READ_STATUS, adr); | |
| status = map_read(map, adr); | |
| if((status.x[0] & SR_READY) == SR_READY) | |
| break; | |
| udelay(100); | |
| } | |
| if(i==1000){ | |
| printk("sharp: timed out unlocking block\n"); | |
| } | |
| if(!(status.x[0] & SR_ERRORS)){ | |
| sharp_send_cmd(map, CMD_RESET, adr); | |
| chip->state = FL_READY; | |
| return; | |
| } | |
| printk("sharp: error unlocking block at addr=%08lx status=%08lx\n", adr, status.x[0]); | |
| sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); | |
| } | |
| #endif | |
| static void sharp_sync(struct mtd_info *mtd) | |
| { | |
| //printk("sharp_sync()\n"); | |
| } | |
| static int sharp_suspend(struct mtd_info *mtd) | |
| { | |
| printk("sharp_suspend()\n"); | |
| return -EINVAL; | |
| } | |
| static void sharp_resume(struct mtd_info *mtd) | |
| { | |
| printk("sharp_resume()\n"); | |
| } | |
| static void sharp_destroy(struct mtd_info *mtd) | |
| { | |
| printk("sharp_destroy()\n"); | |
| } | |
| static int __init sharp_probe_init(void) | |
| { | |
| printk("MTD Sharp chip driver <ds@lineo.com>\n"); | |
| register_mtd_chip_driver(&sharp_chipdrv); | |
| return 0; | |
| } | |
| static void __exit sharp_probe_exit(void) | |
| { | |
| unregister_mtd_chip_driver(&sharp_chipdrv); | |
| } | |
| module_init(sharp_probe_init); | |
| module_exit(sharp_probe_exit); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_AUTHOR("David Schleef <ds@schleef.org>"); | |
| MODULE_DESCRIPTION("Old MTD chip driver for pre-CFI Sharp flash chips"); |