From patchwork Sat Jun 13 10:59:58 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mike Frysinger X-Patchwork-Id: 28668 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [18.85.46.34]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by bilbo.ozlabs.org (Postfix) with ESMTPS id 033B5B71F6 for ; Sat, 13 Jun 2009 21:03:09 +1000 (EST) Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.69 #1 (Red Hat Linux)) id 1MFQyd-0000pH-Pl; Sat, 13 Jun 2009 11:00:47 +0000 Received: from smtp.gentoo.org ([140.211.166.183]) by bombadil.infradead.org with esmtps (Exim 4.69 #1 (Red Hat Linux)) id 1MFQyT-0000nu-8H for linux-mtd@lists.infradead.org; Sat, 13 Jun 2009 11:00:44 +0000 Received: from localhost.localdomain (localhost [127.0.0.1]) by smtp.gentoo.org (Postfix) with ESMTP id 6BCBD65D5C for ; Sat, 13 Jun 2009 11:00:10 +0000 (UTC) From: Mike Frysinger To: linux-mtd@lists.infradead.org Subject: [PATCH] [MTD] [CHIPS] stm_flash: new ST PSD4256G compatible flash chip driver Date: Sat, 13 Jun 2009 06:59:58 -0400 Message-Id: <1244890798-10908-1-git-send-email-vapier@gentoo.org> X-Mailer: git-send-email 1.6.3.1 X-Spam-Score: -4.0 (----) X-Spam-Report: SpamAssassin version 3.2.5 on bombadil.infradead.org summary: Content analysis details: (-4.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -4.0 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [140.211.166.183 listed in list.dnswl.org] X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.11 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-mtd-bounces@lists.infradead.org Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org The ST PSD4256G Flash In-System Programming (ISP) part is a multifunction device. Since all of the different pieces are memory mapped, this is a driver for the NOR flash component. All the other pieces can use generic memory based drivers. Unfortunately, this is not a CFI compliant part in any way, so we need a dedicated chip driver for it. Signed-off-by: Mike Frysinger --- drivers/mtd/chips/Kconfig | 8 + drivers/mtd/chips/Makefile | 1 + drivers/mtd/chips/stm_flash.c | 747 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 756 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/chips/stm_flash.c diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig index 9408099..881c6bc 100644 --- a/drivers/mtd/chips/Kconfig +++ b/drivers/mtd/chips/Kconfig @@ -207,6 +207,14 @@ config MTD_CFI_STAA sets which a CFI-compliant chip may claim to implement. This code provides support for one of those command sets. +config MTD_PSD4256G + tristate "ST PSD4256G compatible flash chip support" + help + Driver for flashes compatible with ST PSD4256G. This is a non-CFI + and non-JEDEC compliant flash and you really shouldn't buy this. + + If unsure, say N here. + config MTD_CFI_UTIL tristate diff --git a/drivers/mtd/chips/Makefile b/drivers/mtd/chips/Makefile index 3658241..376d78a 100644 --- a/drivers/mtd/chips/Makefile +++ b/drivers/mtd/chips/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_MTD_JEDECPROBE) += jedec_probe.o obj-$(CONFIG_MTD_RAM) += map_ram.o obj-$(CONFIG_MTD_ROM) += map_rom.o obj-$(CONFIG_MTD_ABSENT) += map_absent.o +obj-$(CONFIG_MTD_PSD4256G) += stm_flash.o diff --git a/drivers/mtd/chips/stm_flash.c b/drivers/mtd/chips/stm_flash.c new file mode 100644 index 0000000..cd9ed59 --- /dev/null +++ b/drivers/mtd/chips/stm_flash.c @@ -0,0 +1,747 @@ +/* + * Driver for ST PSD4256G flash + * + * Copyright 2004-2009 Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#define pr_fmt(fmt) "stm_flash: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses */ +#define ADDR_MANUFACTURER 0x0000 +#define ADDR_DEVICE_ID 0x0002 +#define ADDR_UNLOCK_1 0x0AAA +#define ADDR_UNLOCK_2 0x0554 + +/* Commands */ +#define CMD_UNLOCK_DATA_1 0x00aa +#define CMD_UNLOCK_DATA_2 0x0055 +#define CMD_MANUFACTURER_UNLOCK_DATA 0x0090 +#define CMD_PROGRAM_UNLOCK_DATA 0x00A0 +#define CMD_RESET_DATA 0x00F0 +#define CMD_SECTOR_ERASE_UNLOCK_DATA 0x0080 +#define CMD_SECTOR_ERASE_UNLOCK_DATA_2 0x0030 + +#define D6_MASK 0x40 + +struct stm_flash_private { + unsigned long chipshift; + struct flchip chip; +}; + +static void send_unlock(struct map_info *map, unsigned long base) +{ + map_word test; + test.x[0] = 0x00aa; + map_write(map, test, base + ADDR_UNLOCK_1); + test.x[0] = 0x0055; + map_write(map, test, base + ADDR_UNLOCK_2); +} + +static void send_cmd(struct map_info *map, unsigned long base, + unsigned long cmd) +{ + map_word test; + test.x[0] = cmd; + send_unlock(map, base); + map_write(map, test, base + ADDR_UNLOCK_1); +} + +static void send_cmd_to_addr(struct map_info *map, unsigned long base, + unsigned long cmd, unsigned long addr) +{ + map_word test; + test.x[0] = cmd; + send_unlock(map, base); + map_write(map, test, addr); +} + +static void stm_flash_sync(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct stm_flash_private *private = map->fldrv_priv; + struct flchip *chip; + DECLARE_WAITQUEUE(wait, current); + + chip = &private->chip; + + retry: + spin_lock_bh(chip->mutex); + + switch (chip->state) { + case FL_READY: + case FL_STATUS: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + chip->oldstate = chip->state; + chip->state = FL_SYNCING; + case FL_SYNCING: + spin_unlock_bh(chip->mutex); + break; + default: + /* Not an idle state */ + add_wait_queue(&chip->wq, &wait); + + spin_unlock_bh(chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + + goto retry; + } + + chip = &private->chip; + + spin_lock_bh(chip->mutex); + + if (chip->state == FL_SYNCING) { + chip->state = chip->oldstate; + wake_up(&chip->wq); + } + spin_unlock_bh(chip->mutex); +} + +static int read_one_chip(struct map_info *map, struct flchip *chip, + loff_t addr, size_t len, unsigned char *buf) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long timeo = jiffies + HZ; + + retry: + spin_lock_bh(chip->mutex); + + if (chip->state != FL_READY) { + pr_info("waiting for chip to read, state = %d\n", + chip->state); + set_current_state(TASK_UNINTERRUPTIBLE); + 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; + } + + addr += chip->start; + + chip->state = FL_READY; + + map_copy_from(map, buf, addr, len); + + wake_up(&chip->wq); + spin_unlock_bh(chip->mutex); + + return 0; +} + +static int stm_flash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, unsigned char *buf) +{ + struct map_info *map = mtd->priv; + struct stm_flash_private *private = map->fldrv_priv; + unsigned long offset; + int chipnum, ret = 0; + + if ((from + len) > mtd->size) { + pr_warning("read request past end of device (0x%lx)\n", + (unsigned long)from + len); + return -EINVAL; + } + + /* Offset within the first chip that the first read should start. */ + chipnum = (from >> private->chipshift); + offset = from - (chipnum << private->chipshift); + + *retlen = 0; + + while (len) { + unsigned long this_len; + + if ((len + offset - 1) >> private->chipshift) + this_len = (1 << private->chipshift) - offset; + else + this_len = len; + + ret = read_one_chip(map, &private->chip, offset, + this_len, buf); + + if (ret) + break; + + *retlen += this_len; + len -= this_len; + buf += this_len; + + offset = 0; + chipnum++; + } + + return ret; +} + +static int flash_is_busy(struct map_info *map, unsigned long addr) +{ + unsigned short toggled; + map_word read11, read21; + + read11 = map_read(map, addr); + read21 = map_read(map, addr); + + toggled = (unsigned short)read11.x[0] ^ (unsigned short)read21.x[0]; + + toggled &= (((unsigned short)1) << 6); + return toggled; +} + +static int write_one_word(struct map_info *map, struct flchip *chip, + unsigned long addr, unsigned long datum) +{ + unsigned long timeo = jiffies + HZ; + DECLARE_WAITQUEUE(wait, current); + int ret = 0; + int times_left; + map_word test; + + retry: + spin_lock_bh(chip->mutex); + + if (chip->state != FL_READY) { + pr_info("%s: waiting for chip to write, state = %d\n", + map->name, chip->state); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + + spin_unlock_bh(chip->mutex); + + schedule(); + remove_wait_queue(&chip->wq, &wait); + pr_info("%s: woke up to write\n", map->name); + if (signal_pending(current)) + return -EINTR; + + timeo = jiffies + HZ; + + goto retry; + } + + chip->state = FL_WRITING; + + addr += chip->start; + + send_cmd(map, chip->start, CMD_PROGRAM_UNLOCK_DATA); + + test.x[0] = datum; + map_write(map, test, addr); + + times_left = 50000; + while (times_left-- && flash_is_busy(map, addr)) { + if (need_resched()) { + spin_unlock_bh(chip->mutex); + schedule(); + spin_lock_bh(chip->mutex); + } + } + + if (!times_left) { + pr_warning("write to 0x%lx timed out!\n", addr); + ret = -EIO; + } else { + unsigned long verify; + map_word test; + + test = map_read(map, addr); + verify = test.x[0]; + if (verify != datum) { + pr_warning("write to 0x%lx failed " + "datum = %lx, verify = %lx\n", + addr, datum, verify); + ret = -EIO; + } + } + chip->state = FL_READY; + wake_up(&chip->wq); + spin_unlock_bh(chip->mutex); + + return ret; +} + +static int stm_flash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const unsigned char *buf) +{ + struct map_info *map = mtd->priv; + struct stm_flash_private *private = map->fldrv_priv; + int ret = 0; + int chipnum; + unsigned long offset; + + *retlen = 0; + if (!len) + return 0; + + chipnum = to >> private->chipshift; + offset = to - (chipnum << private->chipshift); + + /* If it's not bus-aligned, do the first byte write. */ + if (offset & (map->bankwidth - 1)) { + unsigned long bus_offset = offset & ~(map->bankwidth - 1); + int i = offset - bus_offset; + int n = 0; + unsigned char tmp_buf[4]; + unsigned long datum; + + map_copy_from(map, tmp_buf, + bus_offset + private->chip.start, + map->bankwidth); + while (len && i < map->bankwidth) + tmp_buf[i++] = buf[n++], len--; + + if (map->bankwidth == 2) + datum = *(__u16 *)tmp_buf; + else if (map->bankwidth == 4) + datum = *(__u32 *)tmp_buf; + else + return -EINVAL; + + ret = write_one_word(map, &private->chip, bus_offset, + datum); + + if (ret) + return ret; + + offset += n; + buf += n; + (*retlen) += n; + + if (offset >> private->chipshift) + return 0; + } + + /* We are now aligned, write as much as possible. */ + while (len >= map->bankwidth) { + unsigned long datum; + + if (map->bankwidth == 1) + datum = *(unsigned char *)buf; + else if (map->bankwidth == 2) + datum = *(unsigned short *)buf; + else if (map->bankwidth == 4) + datum = *(unsigned long *)buf; + else + return -EINVAL; + + ret = write_one_word(map, &private->chip, offset, + datum); + + if (ret) + return ret; + + offset += map->bankwidth; + buf += map->bankwidth; + (*retlen) += map->bankwidth; + len -= map->bankwidth; + + if (offset >> private->chipshift) + return 0; + } + + if (len & (map->bankwidth - 1)) { + int i = 0, n = 0; + unsigned char tmp_buf[2]; + unsigned long datum; + + map_copy_from(map, tmp_buf, + offset + private->chip.start, + map->bankwidth); + + while (len--) + tmp_buf[i++] = buf[n++]; + + if (map->bankwidth == 2) + datum = *(unsigned short *)tmp_buf; + else if (map->bankwidth == 4) + datum = *(unsigned long *)tmp_buf; + else + return -EINVAL; + + ret = write_one_word(map, &private->chip, offset, + datum); + + if (ret) + return ret; + + (*retlen) += n; + } + + return 0; +} + +static int erase_one_block(struct map_info *map, struct flchip *chip, + unsigned long addr, unsigned long size) +{ + unsigned long timeo = jiffies + HZ; + DECLARE_WAITQUEUE(wait, current); + + retry: + spin_lock_bh(chip->mutex); + + if (chip->state != FL_READY) { + set_current_state(TASK_UNINTERRUPTIBLE); + 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; + } + + chip->state = FL_ERASING; + + addr += chip->start; + + send_cmd(map, chip->start, CMD_SECTOR_ERASE_UNLOCK_DATA); + send_cmd_to_addr(map, chip->start, CMD_SECTOR_ERASE_UNLOCK_DATA_2, addr); + + timeo = jiffies + (HZ * 20); + + spin_unlock_bh(chip->mutex); + schedule_timeout(HZ); + spin_lock_bh(chip->mutex); + + while (flash_is_busy(map, chip->start)) { + if (chip->state != FL_ERASING) { + /* Someone's suspended the erase. Sleep. */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + + spin_unlock_bh(chip->mutex); + pr_info("erase suspended, sleeping\n"); + schedule(); + remove_wait_queue(&chip->wq, &wait); + + if (signal_pending(current)) + return -EINTR; + + timeo = jiffies + (HZ*2); + spin_lock_bh(chip->mutex); + continue; + } + + /* OK Still waiting */ + if (time_after(jiffies, timeo)) { + chip->state = FL_READY; + spin_unlock_bh(chip->mutex); + pr_warning("waiting for erase to complete timed out\n"); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while, and retry. */ + spin_unlock_bh(chip->mutex); + + if (need_resched()) + schedule(); + else + udelay(1); + + spin_lock_bh(chip->mutex); + } + + { + /* Verify every single word */ + int address; + int error = 0; + int verify; + map_word test; + + for (address = addr; address < (addr + size); address += 2) { + test = map_read(map, address); + verify = test.x[0]; + if (verify != 0xFFFF) { + error = 1; + break; + } + } + + if (error) { + chip->state = FL_READY; + spin_unlock_bh(chip->mutex); + pr_warning("verify error at 0x%x, size %ld\n", + address, size); + return -EIO; + } + } + + chip->state = FL_READY; + wake_up(&chip->wq); + spin_unlock_bh(chip->mutex); + + return 0; +} + +static int stm_flash_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct map_info *map = mtd->priv; + struct stm_flash_private *private = map->fldrv_priv; + unsigned long addr, len, shift; + int chipnum; + int ret = 0; + int i, first; + struct mtd_erase_region_info *regions = mtd->eraseregions; + + if (instr->addr > mtd->size) + return -EINVAL; + + if ((instr->len + instr->addr) > mtd->size) + return -EINVAL; + + /* + * Check that both start and end of the requested erase are aligned + * with the erasesize at the appropriate addresses. + */ + i = 0; + + /* + * Skip all erase regions which are ended before the start of the + * requested erase. Actually, to save on the calculations, we skip + * to the first erase region which starts after the start of the + * requested erase, and then go back one. + */ + while ((i < mtd->numeraseregions) && + (instr->addr >= regions[i].offset)) + ++i; + --i; + + /* + * OK. Now i is pointing at the erase region in which this erase + * request starts. Check the start of the requested erase range + * is aligned with the erase size which is in effect here. + */ + if (instr->addr & (regions[i].erasesize - 1)) + return -EINVAL; + + /* + * Remember the erase region we start on. + */ + first = i; + + /* + * Next, theck that the end of the requested erase is aligned with + * the erase region at that address. + */ + while ((i < mtd->numeraseregions) && + ((instr->addr + instr->len) >= regions[i].offset)) + ++i; + --i; + + if ((instr->addr + instr->len) & (regions[i].erasesize - 1)) + return -EINVAL; + + chipnum = instr->addr >> private->chipshift; + addr = instr->addr - (chipnum << private->chipshift); + len = instr->len; + + i = first; + shift = (1 << private->chipshift) - 1; + while (len) { + ret = erase_one_block(map, &private->chip, addr, + regions[i].erasesize); + + if (ret) + return ret; + + addr += regions[i].erasesize; + len -= regions[i].erasesize; + + if ((addr & shift) == + ((regions[i].offset + + (regions[i].erasesize * regions[i].numblocks)) + & shift)) + ++i; + + if (addr & ~shift) + break; + } + + instr->state = MTD_ERASE_DONE; + if (instr->callback) + instr->callback(instr); + + return 0; +} + +static void stm_flash_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct stm_flash_private *private = map->fldrv_priv; + kfree(private); +} + +struct stm_flash_info { + u8 mfr_id, dev_id; + char *name; + unsigned long size; + int numeraseregions; + struct mtd_erase_region_info regions[1]; +}; + +const struct stm_flash_info stm_data[] = { + { + .mfr_id = 0x20, + .dev_id = 0xe9, + .name = "STM PSD4256G6V", + .size = 0x100000, + .numeraseregions = 1, + .regions = { + { + .offset = 0x000000, + .erasesize = 0x10000, + .numblocks = 16 + }, + }, + }, +}; + +static int stm_probe_new_chip(struct mtd_info *mtd) +{ + const struct stm_flash_info *std; + struct map_info *map = mtd->priv; + int i; + u8 mfr_id, dev_id; + map_word mfr_word, dev_word; + + /* Enter autoselect mode */ + send_cmd(map, 0, CMD_RESET_DATA); + send_cmd(map, 0, CMD_MANUFACTURER_UNLOCK_DATA); + + mfr_word = map_read(map, ADDR_MANUFACTURER); + dev_word = map_read(map, ADDR_DEVICE_ID); + mfr_id = mfr_word.x[0] & 0xFF; + dev_id = dev_word.x[0] & 0xFF; + + /* Exit autoselect mode */ + send_cmd(map, 0, CMD_RESET_DATA); + + for (i = 0; i < ARRAY_SIZE(stm_data); ++i) { + std = &stm_data[i]; + if (mfr_id == std->mfr_id && dev_id == std->dev_id) + break; + } + if (i == ARRAY_SIZE(stm_data)) { + pr_warning("unknown mfr/dev id: 0x%02x 0x%02x\n", mfr_id, dev_id); + return 1; + } + + mtd->size += std->size; + mtd->numeraseregions += std->numeraseregions; + + mtd->eraseregions = kmalloc(sizeof(*mtd->eraseregions) * + mtd->numeraseregions, GFP_KERNEL); + if (!mtd->eraseregions) { + pr_warning("failed to allocate memory for MTD erase region\n"); + return 1; + } + + for (i = 0; i < std->numeraseregions; ++i) { + mtd->eraseregions[i].offset = std->regions[i].offset; + mtd->eraseregions[i].erasesize = std->regions[i].erasesize; + mtd->eraseregions[i].numblocks = std->regions[i].numblocks; + if (mtd->erasesize < mtd->eraseregions[i].erasesize) + mtd->erasesize = mtd->eraseregions[i].erasesize; + } + + pr_info("found %s device at %p in %d-bit bank\n", + std->name, map->virt, map->bankwidth * 8); + + return 0; +} + +static struct mtd_info *stm_flash_probe(struct map_info *map); + +static struct mtd_chip_driver stm_flash_chipdrv = { + .probe = stm_flash_probe, + .destroy = stm_flash_destroy, + .name = "stm_flash", + .module = THIS_MODULE +}; + +static struct mtd_info *stm_flash_probe(struct map_info *map) +{ + struct mtd_info *mtd; + struct flchip *chip; + struct stm_flash_private *private; + unsigned long size; + + mtd = kzalloc(sizeof(*mtd) + sizeof(*private), GFP_KERNEL); + if (!mtd) { + pr_warning("kmalloc failed for info structure\n"); + return NULL; + } + mtd->priv = map; + map->fldrv_priv = private = (void *)&mtd[1]; + map->fldrv = &stm_flash_chipdrv; + + if (stm_probe_new_chip(mtd)) { + kfree(mtd); + return NULL; + } + + chip = &private->chip; + init_waitqueue_head(&chip->wq); + spin_lock_init(&chip->_spinlock); + chip->start = 0; + chip->state = FL_READY; + chip->mutex = &chip->_spinlock; + for (size = mtd->size; size > 1; size >>= 1) + ++private->chipshift; + + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->name = map->name; + mtd->erase = stm_flash_erase; + mtd->read = stm_flash_read; + mtd->write = stm_flash_write; + mtd->sync = stm_flash_sync; + mtd->writesize = 1; + + return mtd; +} + +static int __init stm_flash_init(void) +{ + register_mtd_chip_driver(&stm_flash_chipdrv); + return 0; +} +module_init(stm_flash_init); + +static void __exit stm_flash_exit(void) +{ + unregister_mtd_chip_driver(&stm_flash_chipdrv); +} +module_exit(stm_flash_exit); + +MODULE_DESCRIPTION("MTD chip driver for ST PSD4256G flashes"); +MODULE_LICENSE("GPL");