📄 amd_flash.c
字号:
/* * MTD map driver for AMD compatible flash chips (non-CFI) * * Author: Jonas Holmberg <jonas.holmberg@axis.com> * * $Id: amd_flash.c,v 1.27 2005/02/04 07:43:09 jonashg Exp $ * * Copyright (c) 2001 Axis Communications AB * * This file is under GPL. * */#include <linux/module.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/errno.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/init.h>#include <linux/mtd/map.h>#include <linux/mtd/mtd.h>#include <linux/mtd/flashchip.h>/* There's no limit. It exists only to avoid realloc. */#define MAX_AMD_CHIPS 8#define DEVICE_TYPE_X8 (8 / 8)#define DEVICE_TYPE_X16 (16 / 8)#define DEVICE_TYPE_X32 (32 / 8)/* Addresses */#define ADDR_MANUFACTURER 0x0000#define ADDR_DEVICE_ID 0x0001#define ADDR_SECTOR_LOCK 0x0002#define ADDR_HANDSHAKE 0x0003#define ADDR_UNLOCK_1 0x0555#define ADDR_UNLOCK_2 0x02AA/* Commands */#define CMD_UNLOCK_DATA_1 0x00AA#define CMD_UNLOCK_DATA_2 0x0055#define CMD_MANUFACTURER_UNLOCK_DATA 0x0090#define CMD_UNLOCK_BYPASS_MODE 0x0020#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 CMD_UNLOCK_SECTOR 0x0060/* Manufacturers */#define MANUFACTURER_AMD 0x0001#define MANUFACTURER_ATMEL 0x001F#define MANUFACTURER_FUJITSU 0x0004#define MANUFACTURER_ST 0x0020#define MANUFACTURER_SST 0x00BF#define MANUFACTURER_TOSHIBA 0x0098/* AMD */#define AM29F800BB 0x2258#define AM29F800BT 0x22D6#define AM29LV800BB 0x225B#define AM29LV800BT 0x22DA#define AM29LV160DT 0x22C4#define AM29LV160DB 0x2249#define AM29BDS323D 0x22D1/* Atmel */#define AT49xV16x 0x00C0#define AT49xV16xT 0x00C2/* Fujitsu */#define MBM29LV160TE 0x22C4#define MBM29LV160BE 0x2249#define MBM29LV800BB 0x225B/* ST - www.st.com */#define M29W800T 0x00D7#define M29W160DT 0x22C4#define M29W160DB 0x2249/* SST */#define SST39LF800 0x2781#define SST39LF160 0x2782/* Toshiba */#define TC58FVT160 0x00C2#define TC58FVB160 0x0043#define D6_MASK 0x40struct amd_flash_private { int device_type; int interleave; int numchips; unsigned long chipshift;// const char *im_name; struct flchip chips[0];};struct amd_flash_info { const __u16 mfr_id; const __u16 dev_id; const char *name; const u_long size; const int numeraseregions; const struct mtd_erase_region_info regions[4];};static int amd_flash_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);static int amd_flash_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);static int amd_flash_erase(struct mtd_info *, struct erase_info *);static void amd_flash_sync(struct mtd_info *);static int amd_flash_suspend(struct mtd_info *);static void amd_flash_resume(struct mtd_info *);static void amd_flash_destroy(struct mtd_info *);static struct mtd_info *amd_flash_probe(struct map_info *map);static struct mtd_chip_driver amd_flash_chipdrv = { .probe = amd_flash_probe, .destroy = amd_flash_destroy, .name = "amd_flash", .module = THIS_MODULE};static const char im_name[] = "amd_flash";static inline __u32 wide_read(struct map_info *map, __u32 addr){ if (map->buswidth == 1) { return map_read8(map, addr); } else if (map->buswidth == 2) { return map_read16(map, addr); } else if (map->buswidth == 4) { return map_read32(map, addr); } return 0;}static inline void wide_write(struct map_info *map, __u32 val, __u32 addr){ if (map->buswidth == 1) { map_write8(map, val, addr); } else if (map->buswidth == 2) { map_write16(map, val, addr); } else if (map->buswidth == 4) { map_write32(map, val, addr); }}static inline __u32 make_cmd(struct map_info *map, __u32 cmd){ const struct amd_flash_private *private = map->fldrv_priv; if ((private->interleave == 2) && (private->device_type == DEVICE_TYPE_X16)) { cmd |= (cmd << 16); } return cmd;}static inline void send_unlock(struct map_info *map, unsigned long base){ wide_write(map, (CMD_UNLOCK_DATA_1 << 16) | CMD_UNLOCK_DATA_1, base + (map->buswidth * ADDR_UNLOCK_1)); wide_write(map, (CMD_UNLOCK_DATA_2 << 16) | CMD_UNLOCK_DATA_2, base + (map->buswidth * ADDR_UNLOCK_2));}static inline void send_cmd(struct map_info *map, unsigned long base, __u32 cmd){ send_unlock(map, base); wide_write(map, make_cmd(map, cmd), base + (map->buswidth * ADDR_UNLOCK_1));}static inline void send_cmd_to_addr(struct map_info *map, unsigned long base, __u32 cmd, unsigned long addr){ send_unlock(map, base); wide_write(map, make_cmd(map, cmd), addr);}static inline int flash_is_busy(struct map_info *map, unsigned long addr, int interleave){ if ((interleave == 2) && (map->buswidth == 4)) { __u32 read1, read2; read1 = wide_read(map, addr); read2 = wide_read(map, addr); return (((read1 >> 16) & D6_MASK) != ((read2 >> 16) & D6_MASK)) || (((read1 & 0xffff) & D6_MASK) != ((read2 & 0xffff) & D6_MASK)); } return ((wide_read(map, addr) & D6_MASK) != (wide_read(map, addr) & D6_MASK));}static inline void unlock_sector(struct map_info *map, unsigned long sect_addr, int unlock){ /* Sector lock address. A6 = 1 for unlock, A6 = 0 for lock */ int SLA = unlock ? (sect_addr | (0x40 * map->buswidth)) : (sect_addr & ~(0x40 * map->buswidth)) ; __u32 cmd = make_cmd(map, CMD_UNLOCK_SECTOR); wide_write(map, make_cmd(map, CMD_RESET_DATA), 0); wide_write(map, cmd, SLA); /* 1st cycle: write cmd to any address */ wide_write(map, cmd, SLA); /* 2nd cycle: write cmd to any address */ wide_write(map, cmd, SLA); /* 3rd cycle: write cmd to SLA */}static inline int is_sector_locked(struct map_info *map, unsigned long sect_addr){ int status; wide_write(map, CMD_RESET_DATA, 0); send_cmd(map, sect_addr, CMD_MANUFACTURER_UNLOCK_DATA); /* status is 0x0000 for unlocked and 0x0001 for locked */ status = wide_read(map, sect_addr + (map->buswidth * ADDR_SECTOR_LOCK)); wide_write(map, CMD_RESET_DATA, 0); return status;}static int amd_flash_do_unlock(struct mtd_info *mtd, loff_t ofs, size_t len, int is_unlock){ struct map_info *map; struct mtd_erase_region_info *merip; int eraseoffset, erasesize, eraseblocks; int i; int retval = 0; int lock_status; map = mtd->priv; /* Pass the whole chip through sector by sector and check for each sector if the sector and the given interval overlap */ for(i = 0; i < mtd->numeraseregions; i++) { merip = &mtd->eraseregions[i]; eraseoffset = merip->offset; erasesize = merip->erasesize; eraseblocks = merip->numblocks; if (ofs > eraseoffset + erasesize) continue; while (eraseblocks > 0) { if (ofs < eraseoffset + erasesize && ofs + len > eraseoffset) { unlock_sector(map, eraseoffset, is_unlock); lock_status = is_sector_locked(map, eraseoffset); if (is_unlock && lock_status) { printk("Cannot unlock sector at address %x length %xx\n", eraseoffset, merip->erasesize); retval = -1; } else if (!is_unlock && !lock_status) { printk("Cannot lock sector at address %x length %x\n", eraseoffset, merip->erasesize); retval = -1; } } eraseoffset += erasesize; eraseblocks --; } } return retval;}static int amd_flash_unlock(struct mtd_info *mtd, loff_t ofs, size_t len){ return amd_flash_do_unlock(mtd, ofs, len, 1);}static int amd_flash_lock(struct mtd_info *mtd, loff_t ofs, size_t len){ return amd_flash_do_unlock(mtd, ofs, len, 0);}/* * Reads JEDEC manufacturer ID and device ID and returns the index of the first * matching table entry (-1 if not found or alias for already found chip). */ static int probe_new_chip(struct mtd_info *mtd, __u32 base, struct flchip *chips, struct amd_flash_private *private, const struct amd_flash_info *table, int table_size){ __u32 mfr_id; __u32 dev_id; struct map_info *map = mtd->priv; struct amd_flash_private temp; int i; temp.device_type = DEVICE_TYPE_X16; // Assume X16 (FIXME) temp.interleave = 2; map->fldrv_priv = &temp; /* Enter autoselect mode. */ send_cmd(map, base, CMD_RESET_DATA); send_cmd(map, base, CMD_MANUFACTURER_UNLOCK_DATA); mfr_id = wide_read(map, base + (map->buswidth * ADDR_MANUFACTURER)); dev_id = wide_read(map, base + (map->buswidth * ADDR_DEVICE_ID)); if ((map->buswidth == 4) && ((mfr_id >> 16) == (mfr_id & 0xffff)) && ((dev_id >> 16) == (dev_id & 0xffff))) { mfr_id &= 0xffff; dev_id &= 0xffff; } else { temp.interleave = 1; } for (i = 0; i < table_size; i++) { if ((mfr_id == table[i].mfr_id) && (dev_id == table[i].dev_id)) { if (chips) { int j; /* Is this an alias for an already found chip? * In that case that chip should be in * autoselect mode now. */ for (j = 0; j < private->numchips; j++) { __u32 mfr_id_other; __u32 dev_id_other; mfr_id_other = wide_read(map, chips[j].start + (map->buswidth * ADDR_MANUFACTURER )); dev_id_other = wide_read(map, chips[j].start + (map->buswidth * ADDR_DEVICE_ID)); if (temp.interleave == 2) { mfr_id_other &= 0xffff; dev_id_other &= 0xffff; } if ((mfr_id_other == mfr_id) && (dev_id_other == dev_id)) { /* Exit autoselect mode. */ send_cmd(map, base, CMD_RESET_DATA); return -1; } } if (private->numchips == MAX_AMD_CHIPS) { printk(KERN_WARNING "%s: Too many flash chips " "detected. Increase " "MAX_AMD_CHIPS from %d.\n", map->name, MAX_AMD_CHIPS); return -1; } chips[private->numchips].start = base; chips[private->numchips].state = FL_READY; chips[private->numchips].mutex = &chips[private->numchips]._spinlock; private->numchips++; } printk("%s: Found %d x %ldMiB %s at 0x%x\n", map->name, temp.interleave, (table[i].size)/(1024*1024), table[i].name, base); mtd->size += table[i].size * temp.interleave; mtd->numeraseregions += table[i].numeraseregions; break; } } /* Exit autoselect mode. */ send_cmd(map, base, CMD_RESET_DATA); if (i == table_size) { printk(KERN_DEBUG "%s: unknown flash device at 0x%x, " "mfr id 0x%x, dev id 0x%x\n", map->name, base, mfr_id, dev_id); map->fldrv_priv = NULL; return -1; } private->device_type = temp.device_type; private->interleave = temp.interleave; return i;}static struct mtd_info *amd_flash_probe(struct map_info *map){ static const struct amd_flash_info table[] = { { .mfr_id = MANUFACTURER_AMD, .dev_id = AM29LV160DT, .name = "AMD AM29LV160DT", .size = 0x00200000, .numeraseregions = 4, .regions = { { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 }, { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 }, { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 } } }, { .mfr_id = MANUFACTURER_AMD, .dev_id = AM29LV160DB, .name = "AMD AM29LV160DB", .size = 0x00200000, .numeraseregions = 4, .regions = { { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 }, { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 }, { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 }, { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 } } }, { .mfr_id = MANUFACTURER_TOSHIBA, .dev_id = TC58FVT160, .name = "Toshiba TC58FVT160", .size = 0x00200000, .numeraseregions = 4, .regions = { { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 }, { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 }, { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 }, { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 } } }, { .mfr_id = MANUFACTURER_FUJITSU, .dev_id = MBM29LV160TE, .name = "Fujitsu MBM29LV160TE", .size = 0x00200000,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -