Skip to content

Commit

Permalink
mtd: m25p80: Add support for CAT25xxx serial EEPROMs
Browse files Browse the repository at this point in the history
CAT25 chips (as manufactured by On Semiconductor, previously Catalyst
Semiconductor) are similar to the original M25Px0 chips, except:

- Address width can vary (1-2 bytes, in contrast to 3 bytes in M25P
  chips). So, implement convenient m25p_addr2cmd() and m25p_cmdsz()
  calls, and place address width information into flash_info struct;

- Page size can vary, therefore we shouldn't hardcode it, so get rid
  of FLASH_PAGESIZE definition, and place the page size information
  into flash_info struct;

- CAT25 EEPROMs don't need to be erased, so add NO_ERASE flag, and
  propagate it to the mtd subsystem.

[dwmw2: Fix up for conflicts with DMA safety patch]
Signed-off-by: Anton Vorontsov <[email protected]>
Signed-off-by: David Woodhouse <[email protected]>
  • Loading branch information
Anton Vorontsov authored and David Woodhouse committed Nov 30, 2009
1 parent 18c6182 commit 837479d
Showing 1 changed file with 69 additions and 45 deletions.
114 changes: 69 additions & 45 deletions drivers/mtd/devices/m25p80.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
#include <linux/spi/spi.h>
#include <linux/spi/flash.h>


#define FLASH_PAGESIZE 256

/* Flash opcodes. */
#define OPCODE_WREN 0x06 /* Write enable */
#define OPCODE_RDSR 0x05 /* Read status register */
Expand Down Expand Up @@ -61,7 +58,7 @@

/* Define max times to check status register before we give up. */
#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */
#define CMD_SIZE 4
#define MAX_CMD_SIZE 4

#ifdef CONFIG_M25PXX_USE_FAST_READ
#define OPCODE_READ OPCODE_FAST_READ
Expand All @@ -78,6 +75,8 @@ struct m25p {
struct mutex lock;
struct mtd_info mtd;
unsigned partitioned:1;
u16 page_size;
u16 addr_width;
u8 erase_opcode;
u8 *command;
};
Expand Down Expand Up @@ -198,6 +197,19 @@ static int erase_chip(struct m25p *flash)
return 0;
}

static void m25p_addr2cmd(struct m25p *flash, unsigned int addr, u8 *cmd)
{
/* opcode is in cmd[0] */
cmd[1] = addr >> (flash->addr_width * 8 - 8);
cmd[2] = addr >> (flash->addr_width * 8 - 16);
cmd[3] = addr >> (flash->addr_width * 8 - 24);
}

static int m25p_cmdsz(struct m25p *flash)
{
return 1 + flash->addr_width;
}

/*
* Erase one sector of flash memory at offset ``offset'' which is any
* address within the sector which should be erased.
Expand All @@ -219,11 +231,9 @@ static int erase_sector(struct m25p *flash, u32 offset)

/* Set up command buffer. */
flash->command[0] = flash->erase_opcode;
flash->command[1] = offset >> 16;
flash->command[2] = offset >> 8;
flash->command[3] = offset;
m25p_addr2cmd(flash, offset, flash->command);

spi_write(flash->spi, flash->command, CMD_SIZE);
spi_write(flash->spi, flash->command, m25p_cmdsz(flash));

return 0;
}
Expand Down Expand Up @@ -325,7 +335,7 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,
* Should add 1 byte DUMMY_BYTE.
*/
t[0].tx_buf = flash->command;
t[0].len = CMD_SIZE + FAST_READ_DUMMY_BYTE;
t[0].len = m25p_cmdsz(flash) + FAST_READ_DUMMY_BYTE;
spi_message_add_tail(&t[0], &m);

t[1].rx_buf = buf;
Expand All @@ -352,13 +362,11 @@ static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,

/* Set up the write data buffer. */
flash->command[0] = OPCODE_READ;
flash->command[1] = from >> 16;
flash->command[2] = from >> 8;
flash->command[3] = from;
m25p_addr2cmd(flash, from, flash->command);

spi_sync(flash->spi, &m);

*retlen = m.actual_length - CMD_SIZE - FAST_READ_DUMMY_BYTE;
*retlen = m.actual_length - m25p_cmdsz(flash) - FAST_READ_DUMMY_BYTE;

mutex_unlock(&flash->lock);

Expand Down Expand Up @@ -396,7 +404,7 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
memset(t, 0, (sizeof t));

t[0].tx_buf = flash->command;
t[0].len = CMD_SIZE;
t[0].len = m25p_cmdsz(flash);
spi_message_add_tail(&t[0], &m);

t[1].tx_buf = buf;
Expand All @@ -414,41 +422,36 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,

/* Set up the opcode in the write buffer. */
flash->command[0] = OPCODE_PP;
flash->command[1] = to >> 16;
flash->command[2] = to >> 8;
flash->command[3] = to;
m25p_addr2cmd(flash, to, flash->command);

/* what page do we start with? */
page_offset = to % FLASH_PAGESIZE;
page_offset = to & (flash->page_size - 1);

/* do all the bytes fit onto one page? */
if (page_offset + len <= FLASH_PAGESIZE) {
if (page_offset + len <= flash->page_size) {
t[1].len = len;

spi_sync(flash->spi, &m);

*retlen = m.actual_length - CMD_SIZE;
*retlen = m.actual_length - m25p_cmdsz(flash);
} else {
u32 i;

/* the size of data remaining on the first page */
page_size = FLASH_PAGESIZE - page_offset;
page_size = flash->page_size - page_offset;

t[1].len = page_size;
spi_sync(flash->spi, &m);

*retlen = m.actual_length - CMD_SIZE;
*retlen = m.actual_length - m25p_cmdsz(flash);

/* write everything in PAGESIZE chunks */
/* write everything in flash->page_size chunks */
for (i = page_size; i < len; i += page_size) {
page_size = len - i;
if (page_size > FLASH_PAGESIZE)
page_size = FLASH_PAGESIZE;
if (page_size > flash->page_size)
page_size = flash->page_size;

/* write the next page to flash */
flash->command[1] = (to + i) >> 16;
flash->command[2] = (to + i) >> 8;
flash->command[3] = (to + i);
m25p_addr2cmd(flash, to + i, flash->command);

t[1].tx_buf = buf + i;
t[1].len = page_size;
Expand All @@ -460,7 +463,7 @@ static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
spi_sync(flash->spi, &m);

if (retlen)
*retlen += m.actual_length - CMD_SIZE;
*retlen += m.actual_length - m25p_cmdsz(flash);
}
}

Expand Down Expand Up @@ -492,7 +495,7 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
memset(t, 0, (sizeof t));

t[0].tx_buf = flash->command;
t[0].len = CMD_SIZE;
t[0].len = m25p_cmdsz(flash);
spi_message_add_tail(&t[0], &m);

t[1].tx_buf = buf;
Expand All @@ -511,27 +514,23 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
/* Start write from odd address. */
if (actual) {
flash->command[0] = OPCODE_BP;
flash->command[1] = to >> 16;
flash->command[2] = to >> 8;
flash->command[3] = to;
m25p_addr2cmd(flash, to, flash->command);

/* write one byte. */
t[1].len = 1;
spi_sync(flash->spi, &m);
ret = wait_till_ready(flash);
if (ret)
goto time_out;
*retlen += m.actual_length - CMD_SIZE;
*retlen += m.actual_length - m25p_cmdsz(flash);
}
to += actual;

flash->command[0] = OPCODE_AAI_WP;
flash->command[1] = to >> 16;
flash->command[2] = to >> 8;
flash->command[3] = to;
m25p_addr2cmd(flash, to, flash->command);

/* Write out most of the data here. */
cmd_sz = CMD_SIZE;
cmd_sz = m25p_cmdsz(flash);
for (; actual < len - 1; actual += 2) {
t[0].len = cmd_sz;
/* write two bytes. */
Expand All @@ -555,18 +554,16 @@ static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
if (actual != len) {
write_enable(flash);
flash->command[0] = OPCODE_BP;
flash->command[1] = to >> 16;
flash->command[2] = to >> 8;
flash->command[3] = to;
t[0].len = CMD_SIZE;
m25p_addr2cmd(flash, to, flash->command);
t[0].len = m25p_cmdsz(flash);
t[1].len = 1;
t[1].tx_buf = buf + actual;

spi_sync(flash->spi, &m);
ret = wait_till_ready(flash);
if (ret)
goto time_out;
*retlen += m.actual_length - CMD_SIZE;
*retlen += m.actual_length - m25p_cmdsz(flash);
write_disable(flash);
}

Expand Down Expand Up @@ -595,8 +592,12 @@ struct flash_info {
unsigned sector_size;
u16 n_sectors;

u16 page_size;
u16 addr_width;

u16 flags;
#define SECT_4K 0x01 /* OPCODE_BE_4K works uniformly */
#define M25P_NO_ERASE 0x02 /* No erase command needed */
};

#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
Expand All @@ -605,9 +606,20 @@ struct flash_info {
.ext_id = (_ext_id), \
.sector_size = (_sector_size), \
.n_sectors = (_n_sectors), \
.page_size = 256, \
.addr_width = 3, \
.flags = (_flags), \
})

#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width) \
((kernel_ulong_t)&(struct flash_info) { \
.sector_size = (_sector_size), \
.n_sectors = (_n_sectors), \
.page_size = (_page_size), \
.addr_width = (_addr_width), \
.flags = M25P_NO_ERASE, \
})

/* NOTE: double check command sets and memory organization when you add
* more flash chips. This current list focusses on newer chips, which
* have been converging on command sets which including JEDEC ID.
Expand Down Expand Up @@ -680,6 +692,13 @@ static const struct spi_device_id m25p_ids[] = {
{ "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) },
{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },

/* Catalyst / On Semiconductor -- non-JEDEC */
{ "cat25c11", CAT25_INFO( 16, 8, 16, 1) },
{ "cat25c03", CAT25_INFO( 32, 8, 16, 2) },
{ "cat25c09", CAT25_INFO( 128, 8, 32, 2) },
{ "cat25c17", CAT25_INFO( 256, 8, 32, 2) },
{ "cat25128", CAT25_INFO(2048, 8, 64, 2) },
{ },
};
MODULE_DEVICE_TABLE(spi, m25p_ids);
Expand Down Expand Up @@ -793,7 +812,7 @@ static int __devinit m25p_probe(struct spi_device *spi)
flash = kzalloc(sizeof *flash, GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->command = kmalloc(CMD_SIZE + FAST_READ_DUMMY_BYTE, GFP_KERNEL);
flash->command = kmalloc(MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE, GFP_KERNEL);
if (!flash->command) {
kfree(flash);
return -ENOMEM;
Expand Down Expand Up @@ -841,7 +860,12 @@ static int __devinit m25p_probe(struct spi_device *spi)
flash->mtd.erasesize = info->sector_size;
}

if (info->flags & M25P_NO_ERASE)
flash->mtd.flags |= MTD_NO_ERASE;

flash->mtd.dev.parent = &spi->dev;
flash->page_size = info->page_size;
flash->addr_width = info->addr_width;

dev_info(&spi->dev, "%s (%lld Kbytes)\n", id->name,
(long long)flash->mtd.size >> 10);
Expand Down

0 comments on commit 837479d

Please sign in to comment.