Skip to content

Commit

Permalink
mtd: m25p80: add support of dual and quad spi protocols to all commands
Browse files Browse the repository at this point in the history
Before this patch, m25p80_read() supported few SPI protocols:
- regular SPI 1-1-1
- SPI Dual Output 1-1-2
- SPI Quad Output 1-1-4
On the other hand, all other m25p80_*() hooks only supported SPI 1-1-1.

However once their Quad mode enabled, Micron and Macronix spi-nor memories
expect all commands to use the SPI 4-4-4 protocol.

Also, once their Dual mode enabled, Micron spi-nor memories expect all
commands to use the SPI-2-2-2 protocol.

So this patch adds support to all currently existing SPI protocols to
cover as many protocols as possible.

Signed-off-by: Cyrille Pitchen <cyrille.pitchen@atmel.com>
  • Loading branch information
Cyrille Pitchen authored and Nicolas Ferre committed Nov 27, 2015
1 parent da74455 commit 1bbf484
Showing 1 changed file with 212 additions and 42 deletions.
254 changes: 212 additions & 42 deletions drivers/mtd/devices/m25p80.c
Expand Up @@ -27,23 +27,111 @@
#include <linux/spi/flash.h>
#include <linux/mtd/spi-nor.h>

#define MAX_CMD_SIZE 6
#define MAX_CMD_SIZE 8
struct m25p {
struct spi_device *spi;
struct spi_nor spi_nor;
struct mtd_info mtd;
u8 command[MAX_CMD_SIZE];
};

static inline int m25p80_proto2nbits(enum spi_protocol proto,
unsigned *code_nbits,
unsigned *addr_nbits,
unsigned *data_nbits)
{
unsigned code, addr, data;

switch (proto) {
case SPI_PROTO_1_1_1:
code = SPI_NBITS_SINGLE;
addr = SPI_NBITS_SINGLE;
data = SPI_NBITS_SINGLE;
break;

case SPI_PROTO_1_1_2:
code = SPI_NBITS_SINGLE;
addr = SPI_NBITS_SINGLE;
data = SPI_NBITS_DUAL;
break;

case SPI_PROTO_1_1_4:
code = SPI_NBITS_SINGLE;
addr = SPI_NBITS_SINGLE;
data = SPI_NBITS_QUAD;
break;

case SPI_PROTO_1_2_2:
code = SPI_NBITS_SINGLE;
addr = SPI_NBITS_DUAL;
data = SPI_NBITS_DUAL;
break;

case SPI_PROTO_1_4_4:
code = SPI_NBITS_SINGLE;
addr = SPI_NBITS_QUAD;
data = SPI_NBITS_QUAD;
break;

case SPI_PROTO_2_2_2:
code = SPI_NBITS_DUAL;
addr = SPI_NBITS_DUAL;
data = SPI_NBITS_DUAL;
break;

case SPI_PROTO_4_4_4:
code = SPI_NBITS_QUAD;
addr = SPI_NBITS_QUAD;
data = SPI_NBITS_QUAD;
break;

default:
return -EINVAL;

}

if (code_nbits)
*code_nbits = code;
if (addr_nbits)
*addr_nbits = addr;
if (data_nbits)
*data_nbits = data;

return 0;
}

static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
unsigned code_nbits, data_nbits;
struct spi_transfer xfers[2];
int ret;

ret = spi_write_then_read(spi, &code, 1, val, len);
/* Get transfer protocols (addr_nbits is not relevant here). */
ret = m25p80_proto2nbits(nor->reg_proto,
&code_nbits, NULL, &data_nbits);
if (ret < 0)
return ret;

/* Set up transfers. */
memset(xfers, 0, sizeof(xfers));

flash->command[0] = code;
xfers[0].len = 1;
xfers[0].tx_buf = flash->command;
xfers[0].tx_nbits = code_nbits;

xfers[1].len = len;
xfers[1].rx_buf = &flash->command[1];
xfers[1].rx_nbits = data_nbits;

/* Process command. */
ret = spi_sync_transfer(spi, xfers, 2);
if (ret < 0)
dev_err(&spi->dev, "error %d reading %x\n", ret, code);
else
memcpy(val, &flash->command[1], len);

return ret;
}
Expand All @@ -67,56 +155,93 @@ static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
unsigned code_nbits, data_nbits, num_xfers = 1;
struct spi_transfer xfers[2];
int ret;

/* Get transfer protocols (addr_nbits is not relevant here). */
ret = m25p80_proto2nbits(nor->reg_proto,
&code_nbits, NULL, &data_nbits);
if (ret < 0)
return ret;

/* Set up transfer(s). */
memset(xfers, 0, sizeof(xfers));

flash->command[0] = opcode;
if (buf)
xfers[0].len = 1;
xfers[0].tx_buf = flash->command;
xfers[0].tx_nbits = code_nbits;

if (buf) {
memcpy(&flash->command[1], buf, len);
if (data_nbits == code_nbits) {
xfers[0].len += len;
} else {
xfers[1].len = len;
xfers[1].tx_buf = &flash->command[1];
xfers[1].tx_nbits = data_nbits;
num_xfers++;
}
}

return spi_write(spi, flash->command, len + 1);
/* Process command. */
return spi_sync_transfer(spi, xfers, num_xfers);
}

static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
struct spi_transfer t[2] = {};
unsigned code_nbits, addr_nbits, data_nbits, num_xfers = 1;
struct spi_transfer xfers[3];
struct spi_message m;
int cmd_sz = m25p_cmdsz(nor);

spi_message_init(&m);
int ret, cmd_sz = m25p_cmdsz(nor);

if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
cmd_sz = 1;

flash->command[0] = nor->program_opcode;
m25p_addr2cmd(nor, to, flash->command);
/* Get transfer protocols. */
ret = m25p80_proto2nbits(nor->write_proto,
&code_nbits, &addr_nbits, &data_nbits);
if (ret < 0) {
*retlen = 0;
return;
}

/* Set up transfers. */
memset(xfers, 0, sizeof(xfers));

t[0].tx_buf = flash->command;
t[0].len = cmd_sz;
spi_message_add_tail(&t[0], &m);
flash->command[0] = nor->program_opcode;
xfers[0].len = 1;
xfers[0].tx_buf = flash->command;
xfers[0].tx_nbits = code_nbits;

if (cmd_sz > 1) {
m25p_addr2cmd(nor, to, flash->command);
if (addr_nbits == code_nbits) {
xfers[0].len += nor->addr_width;
} else {
xfers[1].len = nor->addr_width;
xfers[1].tx_buf = &flash->command[1];
xfers[1].tx_nbits = addr_nbits;
num_xfers++;
}
}

t[1].tx_buf = buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
xfers[num_xfers].len = len;
xfers[num_xfers].tx_buf = buf;
xfers[num_xfers].tx_nbits = data_nbits;
num_xfers++;

/* Process command. */
spi_message_init_with_transfers(&m, xfers, num_xfers);
spi_sync(spi, &m);

*retlen += m.actual_length - cmd_sz;
}

static inline unsigned int m25p80_rx_nbits(struct spi_nor *nor)
{
switch (nor->flash_read) {
case SPI_NOR_DUAL:
return 2;
case SPI_NOR_QUAD:
return 4;
default:
return 0;
}
}

/*
* Read an address range from the nor chip. The address range
* may be any size provided it is within the physical boundaries.
Expand All @@ -126,28 +251,48 @@ static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
struct spi_transfer t[2];
struct spi_message m;
unsigned code_nbits, addr_nbits, data_nbits, num_xfers = 1;
unsigned int dummy = nor->read_dummy;
struct spi_transfer xfers[3];
struct spi_message m;
int ret;

/* Get transfer protocols. */
ret = m25p80_proto2nbits(nor->read_proto,
&code_nbits, &addr_nbits, &data_nbits);
if (ret < 0) {
*retlen = 0;
return ret;
}

/* convert the dummy cycles to the number of bytes */
dummy /= 8;

spi_message_init(&m);
memset(t, 0, (sizeof t));
/* Set up transfers. */
memset(xfers, 0, sizeof(xfers));

flash->command[0] = nor->read_opcode;
m25p_addr2cmd(nor, from, flash->command);
xfers[0].len = 1;
xfers[0].tx_buf = flash->command;
xfers[0].tx_nbits = code_nbits;

t[0].tx_buf = flash->command;
t[0].len = m25p_cmdsz(nor) + dummy;
spi_message_add_tail(&t[0], &m);
m25p_addr2cmd(nor, from, flash->command);
if (addr_nbits == code_nbits) {
xfers[0].len += nor->addr_width + dummy;
} else {
xfers[1].len = nor->addr_width + dummy;
xfers[1].tx_buf = &flash->command[1];
xfers[1].tx_nbits = addr_nbits;
num_xfers++;
}

t[1].rx_buf = buf;
t[1].rx_nbits = m25p80_rx_nbits(nor);
t[1].len = len;
spi_message_add_tail(&t[1], &m);
xfers[num_xfers].len = len;
xfers[num_xfers].rx_buf = buf;
xfers[num_xfers].rx_nbits = data_nbits;
num_xfers++;

/* Process command. */
spi_message_init_with_transfers(&m, xfers, num_xfers);
spi_sync(spi, &m);

*retlen = m.actual_length - m25p_cmdsz(nor) - dummy;
Expand All @@ -157,15 +302,40 @@ static int m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
static int m25p80_erase(struct spi_nor *nor, loff_t offset)
{
struct m25p *flash = nor->priv;
struct spi_device *spi = flash->spi;
unsigned code_nbits, addr_nbits, num_xfers = 1;
struct spi_transfer xfers[2];
int ret;

dev_dbg(nor->dev, "%dKiB at 0x%08x\n",
flash->mtd.erasesize / 1024, (u32)offset);

/* Set up command buffer. */
/* Get transfer protocols (data_nbits is not relevant here). */
ret = m25p80_proto2nbits(nor->erase_proto,
&code_nbits, &addr_nbits, NULL);
if (ret < 0)
return ret;

/* Set up transfers. */
memset(xfers, 0, sizeof(xfers));

flash->command[0] = nor->erase_opcode;
xfers[0].len = 1;
xfers[0].tx_buf = flash->command;
xfers[0].tx_nbits = code_nbits;

m25p_addr2cmd(nor, offset, flash->command);
if (code_nbits == addr_nbits) {
xfers[0].len += nor->addr_width;
} else {
xfers[1].len = nor->addr_width;
xfers[1].tx_buf = &flash->command[1];
xfers[1].tx_nbits = addr_nbits;
num_xfers++;
}

spi_write(flash->spi, flash->command, m25p_cmdsz(nor));
/* Process command. */
spi_sync_transfer(spi, xfers, num_xfers);

return 0;
}
Expand Down

0 comments on commit 1bbf484

Please sign in to comment.