From 6b663bce31fb1e1a78dbca22190e98251628fd4f Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Mon, 20 Oct 2008 17:48:10 +0200 Subject: [PATCH 06/23] add collie flash hack --- arch/arm/mach-sa1100/collie.c | 2 +- drivers/mtd/chips/Kconfig | 8 + drivers/mtd/chips/Makefile | 1 + drivers/mtd/chips/sharp.c | 645 +++++++++++++++++++++++++++++++++++++++++ drivers/mtd/maps/Kconfig | 2 +- 5 files changed, 656 insertions(+), 2 deletions(-) create mode 100644 drivers/mtd/chips/sharp.c diff --git a/arch/arm/mach-sa1100/collie.c b/arch/arm/mach-sa1100/collie.c index 8cf267f..ec673b8 100644 --- a/arch/arm/mach-sa1100/collie.c +++ b/arch/arm/mach-sa1100/collie.c @@ -222,7 +222,7 @@ static void collie_flash_exit(void) gpio_free(COLLIE_GPIO_VPEN); } static struct flash_platform_data collie_flash_data = { - .map_name = "cfi_probe", + .map_name = "sharp", .init = collie_flash_init, .set_vpp = collie_set_vpp, .exit = collie_flash_exit, diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig index 9408099..2dcbd03 100644 --- a/drivers/mtd/chips/Kconfig +++ b/drivers/mtd/chips/Kconfig @@ -241,5 +241,13 @@ config MTD_XIP used for XIP purposes. If you're not sure what this is all about then say N. +config MTD_SHARP + tristate "pre-CFI Sharp chip support" + depends on MTD + help + This option enables support for flash chips using Sharp-compatible + commands, including some which are not CFI-compatible and hence + cannot be used with the CONFIG_MTD_CFI_INTELxxx options. + endmenu diff --git a/drivers/mtd/chips/Makefile b/drivers/mtd/chips/Makefile index 3658241..7283c57 100644 --- a/drivers/mtd/chips/Makefile +++ b/drivers/mtd/chips/Makefile @@ -12,4 +12,5 @@ obj-$(CONFIG_MTD_GEN_PROBE) += gen_probe.o obj-$(CONFIG_MTD_JEDECPROBE) += jedec_probe.o obj-$(CONFIG_MTD_RAM) += map_ram.o obj-$(CONFIG_MTD_ROM) += map_rom.o +obj-$(CONFIG_MTD_SHARP) += sharp.o obj-$(CONFIG_MTD_ABSENT) += map_absent.o diff --git a/drivers/mtd/chips/sharp.c b/drivers/mtd/chips/sharp.c new file mode 100644 index 0000000..046b964 --- /dev/null +++ b/drivers/mtd/chips/sharp.c @@ -0,0 +1,645 @@ +/* + * MTD chip driver for pre-CFI Sharp flash chips + * + * Copyright 2000,2001 David A. Schleef + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) + +#define BLOCK_MASK 0xfffe0000 + +/* Configuration options */ + +#define 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 inline 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 void sharp_udelay(unsigned long i) { + if (in_interrupt()) { + udelay(i); + } else { + schedule(); + } +} + +static struct mtd_info *sharp_probe(struct map_info *map) +{ + struct mtd_info *mtd = NULL; + struct sharp_info *sharp = NULL; + int width; + + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); + if(!mtd) + return NULL; + + sharp = kzalloc(sizeof(*sharp), GFP_KERNEL); + if(!sharp) { + kfree(mtd); + return NULL; + } + + 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; + + sharp->chipshift = 24; + 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] == 0x00b000b0) { + printk("Sharp chip, %lx, %lx, width = %d\n", read0.x[0], read4.x[0], width); + /* Prints b000b0, b000b0, width = 4 on collie */ + 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; + case 0x00b000b0: + /* a6 - LH28F640BFHE 8 64k * 2 chip blocks*/ + mtd->erasesize = 0x10000 * width / 2; + mtd->size = 0x800000 * width / 2; + return width; + 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) +{ + 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: + status = map_read(map, adr); + if ((status.x[0] & SR_READY) == SR_READY) + break; + spin_unlock_bh(chip->mutex); + if (time_after(jiffies, timeo)) { + printk("Waiting for chip to be ready timed out in erase\n"); + return -EIO; + } + sharp_udelay(1); + goto retry; + default: + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + + spin_unlock_bh(chip->mutex); + + sharp_udelay(1); + + set_current_state(TASK_RUNNING); + 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<chipshift)) + thislen = (1<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<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 try; + int i; + map_word data, status; + + status.x[0] = 0; + ret = sharp_wait(map,chip); + if (ret < 0) + return ret; + + for (try=0; try<10; try++) { + long timeo; + + 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; + } +#ifdef AUTOUNLOCK + if (status.x[0] & SR_PROTECT) { /* lock block */ + sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); + sharp_unlock_oneblock(map,chip,adr); + sharp_send_cmd(map, CMD_CLEAR_STATUS, adr); + sharp_send_cmd(map, CMD_RESET, adr); + continue; + } +#endif + 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; + + sharp_release(chip); + + 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; + + 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<chipshift)-1); + len = instr->len; + + while(len){ + ret = sharp_erase_oneblock(map, &sharp->chips[chipnum], adr); + if(ret)return ret; + + if (adr >= 0xfe0000) { + adr += mtd->erasesize / 8; + len -= mtd->erasesize / 8; + } else { + 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 inline 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 * 10; + + 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(); + + spin_lock_bh(chip->mutex); + + remove_wait_queue(&chip->wq, &wait); + set_current_state(TASK_RUNNING); + } + ret = -ETIME; +out: + return ret; +} + +static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + int ret; + map_word status; + + ret = sharp_wait(map,chip); + if (ret < 0) + return ret; + +#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) { + spin_unlock_bh(chip->mutex); + 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); + + sharp_release(chip); + + return -EIO; +} + +#ifdef AUTOUNLOCK +static inline void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + map_word status; + + sharp_send_cmd(map, CMD_CLEAR_BLOCK_LOCKS_1, adr & BLOCK_MASK); + sharp_send_cmd(map, CMD_CLEAR_BLOCK_LOCKS_2, adr & BLOCK_MASK); + + sharp_do_wait_for_ready(map,chip,adr); + + status = map_read(map, adr); + + 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) +{ +} + +static int sharp_suspend(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct sharp_info *sharp = map->fldrv_priv; + int i; + struct flchip *chip; + int ret = 0; + + for (i = 0; !ret && i < sharp->numchips; i++) { + chip = &sharp->chips[i]; + ret = sharp_wait(map,chip); + + if (ret) { + ret = -EAGAIN; + } else { + chip->state = FL_PM_SUSPENDED; + spin_unlock_bh(chip->mutex); + } + } + return ret; +} + +static void sharp_resume(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct sharp_info *sharp = map->fldrv_priv; + int i; + struct flchip *chip; + + for (i = 0; i < sharp->numchips; i++) { + chip = &sharp->chips[i]; + + spin_lock_bh(chip->mutex); + + if (chip->state == FL_PM_SUSPENDED) { + /* We need to force it back to a known state */ + sharp_send_cmd(map, CMD_RESET, chip->start); + chip->state = FL_READY; + wake_up(&chip->wq); + } + + spin_unlock_bh(chip->mutex); + } +} + +static void sharp_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct sharp_info *sharp = map->fldrv_priv; + + kfree(sharp); +} + +static int __init sharp_probe_init(void) +{ + printk("MTD Sharp chip driver \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 "); +MODULE_DESCRIPTION("Old MTD chip driver for pre-CFI Sharp flash chips"); diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 5ea1693..d523464 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig @@ -360,7 +360,7 @@ config MTD_CDB89712 config MTD_SA1100 tristate "CFI Flash device mapped on StrongARM SA11x0" - depends on MTD_CFI && ARCH_SA1100 && MTD_PARTITIONS + depends on (MTD_CFI || MTD_SHARP) && ARCH_SA1100 && MTD_PARTITIONS help This enables access to the flash chips on most platforms based on the SA1100 and SA1110, including the Assabet and the Compaq iPAQ. -- 1.5.6.5