aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mtd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/spi-nor/spi-nor.c209
1 files changed, 143 insertions, 66 deletions
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 4fc632ec18fe..caf0c109cca0 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -246,55 +246,45 @@ struct flash_info {
#define JEDEC_MFR(info) ((info)->id[0])
/**
- * spi_nor_spimem_xfer_data() - helper function to read/write data to
- * flash's memory region
+ * spi_nor_spimem_bounce() - check if a bounce buffer is needed for the data
+ * transfer
* @nor: pointer to 'struct spi_nor'
* @op: pointer to 'struct spi_mem_op' template for transfer
*
- * Return: number of bytes transferred on success, -errno otherwise
+ * If we have to use the bounce buffer, the data field in @op will be updated.
+ *
+ * Return: true if the bounce buffer is needed, false if not
*/
-static ssize_t spi_nor_spimem_xfer_data(struct spi_nor *nor,
- struct spi_mem_op *op)
+static bool spi_nor_spimem_bounce(struct spi_nor *nor, struct spi_mem_op *op)
{
- bool usebouncebuf = false;
- void *rdbuf = NULL;
- const void *buf;
- int ret;
-
- if (op->data.dir == SPI_MEM_DATA_IN)
- buf = op->data.buf.in;
- else
- buf = op->data.buf.out;
-
- if (object_is_on_stack(buf) || !virt_addr_valid(buf))
- usebouncebuf = true;
-
- if (usebouncebuf) {
+ /* op->data.buf.in occupies the same memory as op->data.buf.out */
+ if (object_is_on_stack(op->data.buf.in) ||
+ !virt_addr_valid(op->data.buf.in)) {
if (op->data.nbytes > nor->bouncebuf_size)
op->data.nbytes = nor->bouncebuf_size;
-
- if (op->data.dir == SPI_MEM_DATA_IN) {
- rdbuf = op->data.buf.in;
- op->data.buf.in = nor->bouncebuf;
- } else {
- op->data.buf.out = nor->bouncebuf;
- memcpy(nor->bouncebuf, buf,
- op->data.nbytes);
- }
+ op->data.buf.in = nor->bouncebuf;
+ return true;
}
- ret = spi_mem_adjust_op_size(nor->spimem, op);
- if (ret)
- return ret;
+ return false;
+}
- ret = spi_mem_exec_op(nor->spimem, op);
- if (ret)
- return ret;
+/**
+ * spi_nor_spimem_exec_op() - execute a memory operation
+ * @nor: pointer to 'struct spi_nor'
+ * @op: pointer to 'struct spi_mem_op' template for transfer
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+static int spi_nor_spimem_exec_op(struct spi_nor *nor, struct spi_mem_op *op)
+{
+ int error;
- if (usebouncebuf && op->data.dir == SPI_MEM_DATA_IN)
- memcpy(rdbuf, nor->bouncebuf, op->data.nbytes);
+ error = spi_mem_adjust_op_size(nor->spimem, op);
+ if (error)
+ return error;
- return op->data.nbytes;
+ return spi_mem_exec_op(nor->spimem, op);
}
/**
@@ -315,6 +305,9 @@ static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
SPI_MEM_OP_ADDR(nor->addr_width, from, 1),
SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
SPI_MEM_OP_DATA_IN(len, buf, 1));
+ bool usebouncebuf;
+ ssize_t nbytes;
+ int error;
/* get transfer protocols. */
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto);
@@ -325,7 +318,22 @@ static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
/* convert the dummy cycles to the number of bytes */
op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
- return spi_nor_spimem_xfer_data(nor, &op);
+ usebouncebuf = spi_nor_spimem_bounce(nor, &op);
+
+ if (nor->dirmap.rdesc) {
+ nbytes = spi_mem_dirmap_read(nor->dirmap.rdesc, op.addr.val,
+ op.data.nbytes, op.data.buf.in);
+ } else {
+ error = spi_nor_spimem_exec_op(nor, &op);
+ if (error)
+ return error;
+ nbytes = op.data.nbytes;
+ }
+
+ if (usebouncebuf && nbytes > 0)
+ memcpy(buf, op.data.buf.in, nbytes);
+
+ return nbytes;
}
/**
@@ -364,6 +372,8 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
SPI_MEM_OP_ADDR(nor->addr_width, to, 1),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(len, buf, 1));
+ ssize_t nbytes;
+ int error;
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto);
op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto);
@@ -372,7 +382,20 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
op.addr.nbytes = 0;
- return spi_nor_spimem_xfer_data(nor, &op);
+ if (spi_nor_spimem_bounce(nor, &op))
+ memcpy(nor->bouncebuf, buf, op.data.nbytes);
+
+ if (nor->dirmap.wdesc) {
+ nbytes = spi_mem_dirmap_write(nor->dirmap.wdesc, op.addr.val,
+ op.data.nbytes, op.data.buf.out);
+ } else {
+ error = spi_nor_spimem_exec_op(nor, &op);
+ if (error)
+ return error;
+ nbytes = op.data.nbytes;
+ }
+
+ return nbytes;
}
/**
@@ -1767,7 +1790,6 @@ static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs,
struct mtd_info *mtd = &nor->mtd;
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 tb_mask = SR_TB_BIT5;
- int shift = ffs(mask) - 1;
int pow;
if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
@@ -1778,7 +1800,7 @@ static void stm_get_locked_range(struct spi_nor *nor, u8 sr, loff_t *ofs,
*ofs = 0;
*len = 0;
} else {
- pow = ((sr & mask) ^ mask) >> shift;
+ pow = ((sr & mask) ^ mask) >> SR_BP_SHIFT;
*len = mtd->size >> pow;
if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
*ofs = 0;
@@ -1860,7 +1882,7 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
int ret, status_old, status_new;
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 tb_mask = SR_TB_BIT5;
- u8 shift = ffs(mask) - 1, pow, val;
+ u8 pow, val;
loff_t lock_len;
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
bool use_top;
@@ -1909,7 +1931,7 @@ static int stm_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
* pow = ceil(log2(size / len)) = log2(size) - floor(log2(len))
*/
pow = ilog2(mtd->size) - ilog2(lock_len);
- val = mask - (pow << shift);
+ val = mask - (pow << SR_BP_SHIFT);
if (val & ~mask)
return -EINVAL;
/* Don't "lock" with no region! */
@@ -1946,7 +1968,7 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
int ret, status_old, status_new;
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
u8 tb_mask = SR_TB_BIT5;
- u8 shift = ffs(mask) - 1, pow, val;
+ u8 pow, val;
loff_t lock_len;
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
bool use_top;
@@ -1997,7 +2019,7 @@ static int stm_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
if (lock_len == 0) {
val = 0; /* fully unlocked */
} else {
- val = mask - (pow << shift);
+ val = mask - (pow << SR_BP_SHIFT);
/* Some power-of-two sizes are not supported */
if (val & ~mask)
return -EINVAL;
@@ -2711,9 +2733,9 @@ static const struct flash_info spi_nor_ids[] = {
static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
{
- int tmp;
- u8 *id = nor->bouncebuf;
- const struct flash_info *info;
+ u8 *id = nor->bouncebuf;
+ unsigned int i;
+ int ret;
if (nor->spimem) {
struct spi_mem_op op =
@@ -2722,22 +2744,20 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(SPI_NOR_MAX_ID_LEN, id, 1));
- tmp = spi_mem_exec_op(nor->spimem, &op);
+ ret = spi_mem_exec_op(nor->spimem, &op);
} else {
- tmp = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id,
+ ret = nor->controller_ops->read_reg(nor, SPINOR_OP_RDID, id,
SPI_NOR_MAX_ID_LEN);
}
- if (tmp) {
- dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp);
- return ERR_PTR(tmp);
+ if (ret) {
+ dev_dbg(nor->dev, "error %d reading JEDEC ID\n", ret);
+ return ERR_PTR(ret);
}
- for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) {
- info = &spi_nor_ids[tmp];
- if (info->id_len) {
- if (!memcmp(info->id, id, info->id_len))
- return &spi_nor_ids[tmp];
- }
+ for (i = 0; i < ARRAY_SIZE(spi_nor_ids) - 1; i++) {
+ if (spi_nor_ids[i].id_len &&
+ !memcmp(spi_nor_ids[i].id, id, spi_nor_ids[i].id_len))
+ return &spi_nor_ids[i];
}
dev_err(nor->dev, "unrecognized JEDEC id bytes: %*ph\n",
SPI_NOR_MAX_ID_LEN, id);
@@ -3598,8 +3618,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
return err;
/* Fix endianness of the BFPT DWORDs. */
- for (i = 0; i < BFPT_DWORD_MAX; i++)
- bfpt.dwords[i] = le32_to_cpu(bfpt.dwords[i]);
+ le32_to_cpu_array(bfpt.dwords, BFPT_DWORD_MAX);
/* Number of address bytes. */
switch (bfpt.dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) {
@@ -4057,7 +4076,7 @@ static int spi_nor_parse_smpt(struct spi_nor *nor,
u32 *smpt;
size_t len;
u32 addr;
- int i, ret;
+ int ret;
/* Read the Sector Map Parameter Table. */
len = smpt_header->length * sizeof(*smpt);
@@ -4071,8 +4090,7 @@ static int spi_nor_parse_smpt(struct spi_nor *nor,
goto out;
/* Fix endianness of the SMPT DWORDs. */
- for (i = 0; i < smpt_header->length; i++)
- smpt[i] = le32_to_cpu(smpt[i]);
+ le32_to_cpu_array(smpt, smpt_header->length);
sector_map = spi_nor_get_map_in_use(nor, smpt, smpt_header->length);
if (IS_ERR(sector_map)) {
@@ -4165,8 +4183,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
goto out;
/* Fix endianness of the 4BAIT DWORDs. */
- for (i = 0; i < SFDP_4BAIT_DWORD_MAX; i++)
- dwords[i] = le32_to_cpu(dwords[i]);
+ le32_to_cpu_array(dwords, SFDP_4BAIT_DWORD_MAX);
/*
* Compute the subset of (Fast) Read commands for which the 4-byte
@@ -5260,6 +5277,58 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
}
EXPORT_SYMBOL_GPL(spi_nor_scan);
+static int spi_nor_create_read_dirmap(struct spi_nor *nor)
+{
+ struct spi_mem_dirmap_info info = {
+ .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, 0, 1),
+ SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
+ SPI_MEM_OP_DATA_IN(0, NULL, 1)),
+ .offset = 0,
+ .length = nor->mtd.size,
+ };
+ struct spi_mem_op *op = &info.op_tmpl;
+
+ /* get transfer protocols. */
+ op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto);
+ op->addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto);
+ op->dummy.buswidth = op->addr.buswidth;
+ op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
+
+ /* convert the dummy cycles to the number of bytes */
+ op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8;
+
+ nor->dirmap.rdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem,
+ &info);
+ return PTR_ERR_OR_ZERO(nor->dirmap.rdesc);
+}
+
+static int spi_nor_create_write_dirmap(struct spi_nor *nor)
+{
+ struct spi_mem_dirmap_info info = {
+ .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1),
+ SPI_MEM_OP_ADDR(nor->addr_width, 0, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(0, NULL, 1)),
+ .offset = 0,
+ .length = nor->mtd.size,
+ };
+ struct spi_mem_op *op = &info.op_tmpl;
+
+ /* get transfer protocols. */
+ op->cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto);
+ op->addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto);
+ op->dummy.buswidth = op->addr.buswidth;
+ op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto);
+
+ if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
+ op->addr.nbytes = 0;
+
+ nor->dirmap.wdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem,
+ &info);
+ return PTR_ERR_OR_ZERO(nor->dirmap.wdesc);
+}
+
static int spi_nor_probe(struct spi_mem *spimem)
{
struct spi_device *spi = spimem->spi;
@@ -5321,6 +5390,14 @@ static int spi_nor_probe(struct spi_mem *spimem)
return -ENOMEM;
}
+ ret = spi_nor_create_read_dirmap(nor);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_create_write_dirmap(nor);
+ if (ret)
+ return ret;
+
return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
data ? data->nr_parts : 0);
}