[PATCH] eeprom: at25: Convert the driver to the spi-mem interface
From: Boris Brezillon
Date: Sat Mar 30 2019 - 10:16:57 EST
The AT25 protocol fits pretty well in the spi-mem model. Convert the
at25 spi driver to a spi-mem driver and use the dirmap API instead of
forging SPI messages manually.
This makes the driver compatible with spi-mem-only controllers
(controllers implementing only the spi_mem ops).
Cc: Geert Uytterhoeven <geert@xxxxxxxxxxxxxx>
Signed-off-by: Boris Brezillon <boris.brezillon@xxxxxxxxxxxxx>
---
drivers/misc/eeprom/at25.c | 282 +++++++++++++++++++++++--------------
1 file changed, 176 insertions(+), 106 deletions(-)
diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c
index 99de6939cd5a..818853babbd0 100644
--- a/drivers/misc/eeprom/at25.c
+++ b/drivers/misc/eeprom/at25.c
@@ -17,7 +17,7 @@
#include <linux/sched.h>
#include <linux/nvmem-provider.h>
-#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
#include <linux/spi/eeprom.h>
#include <linux/property.h>
@@ -29,12 +29,17 @@
*/
struct at25_data {
- struct spi_device *spi;
+ struct spi_mem *spimem;
struct mutex lock;
struct spi_eeprom chip;
unsigned addrlen;
struct nvmem_config nvmem_config;
struct nvmem_device *nvmem;
+ void *scratchbuf;
+ struct {
+ struct spi_mem_dirmap_desc *rdesc[2];
+ struct spi_mem_dirmap_desc *wdesc[2];
+ } dirmap;
};
#define AT25_WREN 0x06 /* latch the write enable */
@@ -63,17 +68,89 @@ struct at25_data {
#define io_limit PAGE_SIZE /* bytes */
+static int at25_create_dirmaps(struct at25_data *at25)
+{
+ struct spi_mem_dirmap_info info = {
+ .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_READ, 1),
+ SPI_MEM_OP_ADDR(at25->addrlen, 0, 1),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(0, NULL, 1)),
+ .offset = 0,
+ .length = at25->chip.byte_len,
+ };
+ struct device *dev = &at25->spimem->spi->dev;
+
+ if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)
+ info.length = 256;
+
+ at25->dirmap.rdesc[0] = devm_spi_mem_dirmap_create(dev, at25->spimem,
+ &info);
+ if (IS_ERR(at25->dirmap.rdesc[0]))
+ return PTR_ERR(at25->dirmap.rdesc[0]);
+
+ info.op_tmpl.cmd.opcode = AT25_WRITE;
+ info.op_tmpl.data.dir = SPI_MEM_DATA_OUT;
+
+ at25->dirmap.wdesc[0] = devm_spi_mem_dirmap_create(dev, at25->spimem,
+ &info);
+ if (IS_ERR(at25->dirmap.wdesc[0]))
+ return PTR_ERR(at25->dirmap.wdesc[0]);
+
+ if (!(at25->chip.flags & EE_INSTR_BIT3_IS_ADDR))
+ return 0;
+
+ info.length = at25->chip.byte_len - 256;
+ info.op_tmpl.cmd.opcode = AT25_READ | AT25_INSTR_BIT3;
+ info.op_tmpl.data.dir = SPI_MEM_DATA_IN;
+
+ at25->dirmap.rdesc[1] = devm_spi_mem_dirmap_create(dev, at25->spimem,
+ &info);
+ if (IS_ERR(at25->dirmap.rdesc[1]))
+ return PTR_ERR(at25->dirmap.rdesc[1]);
+
+ info.op_tmpl.cmd.opcode = AT25_WRITE | AT25_INSTR_BIT3;
+ info.op_tmpl.data.dir = SPI_MEM_DATA_OUT;
+
+ at25->dirmap.wdesc[1] = devm_spi_mem_dirmap_create(dev, at25->spimem,
+ &info);
+ if (IS_ERR(at25->dirmap.wdesc[1]))
+ return PTR_ERR(at25->dirmap.wdesc[1]);
+
+ return 0;
+}
+
+static int at25_rdsr(struct at25_data *at25)
+{
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_RDSR, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_IN(1, at25->scratchbuf, 1));
+ int ret;
+
+ ret = spi_mem_exec_op(at25->spimem, &op);
+ if (ret)
+ return ret;
+
+ return *((u8 *)at25->scratchbuf);
+}
+
+static int at25_wren(struct at25_data *at25)
+{
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_WREN, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_NO_DATA);
+
+ return spi_mem_exec_op(at25->spimem, &op);
+}
+
static int at25_ee_read(void *priv, unsigned int offset,
void *val, size_t count)
{
+ struct spi_mem_dirmap_desc *desc;
struct at25_data *at25 = priv;
- char *buf = val;
- u8 command[EE_MAXADDRLEN + 1];
- u8 *cp;
+ unsigned int dirmap_offset;
ssize_t status;
- struct spi_transfer t[2];
- struct spi_message m;
- u8 instr;
if (unlikely(offset >= at25->chip.byte_len))
return -EINVAL;
@@ -82,38 +159,14 @@ static int at25_ee_read(void *priv, unsigned int offset,
if (unlikely(!count))
return -EINVAL;
- cp = command;
-
- instr = AT25_READ;
- if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)
- if (offset >= (1U << (at25->addrlen * 8)))
- instr |= AT25_INSTR_BIT3;
- *cp++ = instr;
-
- /* 8/16/24-bit address is written MSB first */
- switch (at25->addrlen) {
- default: /* case 3 */
- *cp++ = offset >> 16;
- /* fall through */
- case 2:
- *cp++ = offset >> 8;
- /* fall through */
- case 1:
- case 0: /* can't happen: for better codegen */
- *cp++ = offset >> 0;
+ if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR && offset > 255) {
+ desc = at25->dirmap.rdesc[1];
+ dirmap_offset = offset - 256;
+ } else {
+ desc = at25->dirmap.rdesc[0];
+ dirmap_offset = offset;
}
- spi_message_init(&m);
- memset(t, 0, sizeof(t));
-
- t[0].tx_buf = command;
- t[0].len = at25->addrlen + 1;
- spi_message_add_tail(&t[0], &m);
-
- t[1].rx_buf = buf;
- t[1].len = count;
- spi_message_add_tail(&t[1], &m);
-
mutex_lock(&at25->lock);
/* Read it all at once.
@@ -122,8 +175,8 @@ static int at25_ee_read(void *priv, unsigned int offset,
* other devices on the bus need to be accessed regularly or
* this chip is clocked very slowly
*/
- status = spi_sync(at25->spi, &m);
- dev_dbg(&at25->spi->dev, "read %zu bytes at %d --> %zd\n",
+ status = spi_mem_dirmap_read(desc, dirmap_offset, count, val);
+ dev_dbg(&at25->spimem->spi->dev, "read %zu bytes at %d --> %zd\n",
count, offset, status);
mutex_unlock(&at25->lock);
@@ -149,7 +202,7 @@ static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count)
buf_size = at25->chip.page_size;
if (buf_size > io_limit)
buf_size = io_limit;
- bounce = kmalloc(buf_size + at25->addrlen + 1, GFP_KERNEL);
+ bounce = kmalloc(buf_size, GFP_KERNEL);
if (!bounce)
return -ENOMEM;
@@ -158,48 +211,37 @@ static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count)
*/
mutex_lock(&at25->lock);
do {
+ struct spi_mem_dirmap_desc *desc;
unsigned long timeout, retries;
unsigned segment;
- unsigned offset = (unsigned) off;
- u8 *cp = bounce;
+ unsigned int dirmap_offset;
int sr;
- u8 instr;
- *cp = AT25_WREN;
- status = spi_write(at25->spi, cp, 1);
+ status = at25_wren(at25);
if (status < 0) {
- dev_dbg(&at25->spi->dev, "WREN --> %d\n", status);
+ dev_dbg(&at25->spimem->spi->dev, "WREN --> %d\n",
+ status);
break;
}
- instr = AT25_WRITE;
- if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)
- if (offset >= (1U << (at25->addrlen * 8)))
- instr |= AT25_INSTR_BIT3;
- *cp++ = instr;
-
- /* 8/16/24-bit address is written MSB first */
- switch (at25->addrlen) {
- default: /* case 3 */
- *cp++ = offset >> 16;
- /* fall through */
- case 2:
- *cp++ = offset >> 8;
- /* fall through */
- case 1:
- case 0: /* can't happen: for better codegen */
- *cp++ = offset >> 0;
+ if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR && off > 255) {
+ desc = at25->dirmap.wdesc[1];
+ dirmap_offset = off - 256;
+ } else {
+ desc = at25->dirmap.wdesc[0];
+ dirmap_offset = off;
}
/* Write as much of a page as we can */
- segment = buf_size - (offset % buf_size);
+ segment = buf_size - (dirmap_offset % buf_size);
if (segment > count)
segment = count;
- memcpy(cp, buf, segment);
- status = spi_write(at25->spi, bounce,
- segment + at25->addrlen + 1);
- dev_dbg(&at25->spi->dev, "write %u bytes at %u --> %d\n",
- segment, offset, status);
+ memcpy(bounce, buf, segment);
+ status = spi_mem_dirmap_write(desc, dirmap_offset, segment,
+ bounce);
+ dev_dbg(&at25->spimem->spi->dev,
+ "write %u bytes at %u --> %d\n",
+ segment, off, status);
if (status < 0)
break;
@@ -211,10 +253,9 @@ static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count)
timeout = jiffies + msecs_to_jiffies(EE_TIMEOUT);
retries = 0;
do {
-
- sr = spi_w8r8(at25->spi, AT25_RDSR);
+ sr = at25_rdsr(at25);
if (sr < 0 || (sr & AT25_SR_nRDY)) {
- dev_dbg(&at25->spi->dev,
+ dev_dbg(&at25->spimem->spi->dev,
"rdsr --> %d (%02x)\n", sr, sr);
/* at HZ=100, this is sloooow */
msleep(1);
@@ -225,9 +266,9 @@ static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count)
} while (retries++ < 3 || time_before_eq(jiffies, timeout));
if ((sr < 0) || (sr & AT25_SR_nRDY)) {
- dev_err(&at25->spi->dev,
+ dev_err(&at25->spimem->spi->dev,
"write %u bytes offset %u, timeout after %u msecs\n",
- segment, offset,
+ segment, off,
jiffies_to_msecs(jiffies -
(timeout - EE_TIMEOUT)));
status = -ETIMEDOUT;
@@ -304,7 +345,7 @@ static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip)
return 0;
}
-static int at25_probe(struct spi_device *spi)
+static int at25_probe(struct spi_mem *spimem)
{
struct at25_data *at25 = NULL;
struct spi_eeprom chip;
@@ -313,12 +354,12 @@ static int at25_probe(struct spi_device *spi)
int addrlen;
/* Chip description */
- if (!spi->dev.platform_data) {
- err = at25_fw_to_chip(&spi->dev, &chip);
+ if (!spimem->spi->dev.platform_data) {
+ err = at25_fw_to_chip(&spimem->spi->dev, &chip);
if (err)
return err;
} else
- chip = *(struct spi_eeprom *)spi->dev.platform_data;
+ chip = *(struct spi_eeprom *)spimem->spi->dev.platform_data;
/* For now we only support 8/16/24 bit addressing */
if (chip.flags & EE_ADDR1)
@@ -328,37 +369,49 @@ static int at25_probe(struct spi_device *spi)
else if (chip.flags & EE_ADDR3)
addrlen = 3;
else {
- dev_dbg(&spi->dev, "unsupported address type\n");
+ dev_dbg(&spimem->spi->dev, "unsupported address type\n");
return -EINVAL;
}
- /* Ping the chip ... the status register is pretty portable,
- * unlike probing manufacturer IDs. We do expect that system
- * firmware didn't write it in the past few milliseconds!
- */
- sr = spi_w8r8(spi, AT25_RDSR);
- if (sr < 0 || sr & AT25_SR_nRDY) {
- dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr);
- return -ENXIO;
- }
-
- at25 = devm_kzalloc(&spi->dev, sizeof(struct at25_data), GFP_KERNEL);
+ at25 = devm_kzalloc(&spimem->spi->dev, sizeof(struct at25_data),
+ GFP_KERNEL);
if (!at25)
return -ENOMEM;
mutex_init(&at25->lock);
at25->chip = chip;
- at25->spi = spi;
- spi_set_drvdata(spi, at25);
+ at25->spimem = spimem;
+ spi_mem_set_drvdata(spimem, at25);
at25->addrlen = addrlen;
- at25->nvmem_config.name = dev_name(&spi->dev);
- at25->nvmem_config.dev = &spi->dev;
+ /*
+ * Can't be allocated with devm_kmalloc() because we need a DMA-safe
+ * buffer.
+ */
+ at25->scratchbuf = kmalloc(1, GFP_KERNEL);
+
+ /* Ping the chip ... the status register is pretty portable,
+ * unlike probing manufacturer IDs. We do expect that system
+ * firmware didn't write it in the past few milliseconds!
+ */
+ sr = at25_rdsr(at25);
+ if (sr < 0 || sr & AT25_SR_nRDY) {
+ dev_dbg(&spimem->spi->dev, "rdsr --> %d (%02x)\n", sr, sr);
+ err = -ENXIO;
+ goto err_free_scratchbuf;
+ }
+
+ err = at25_create_dirmaps(at25);
+ if (err)
+ goto err_free_scratchbuf;
+
+ at25->nvmem_config.name = dev_name(&spimem->spi->dev);
+ at25->nvmem_config.dev = &spimem->spi->dev;
at25->nvmem_config.read_only = chip.flags & EE_READONLY;
at25->nvmem_config.root_only = true;
at25->nvmem_config.owner = THIS_MODULE;
at25->nvmem_config.compat = true;
- at25->nvmem_config.base_dev = &spi->dev;
+ at25->nvmem_config.base_dev = &spimem->spi->dev;
at25->nvmem_config.reg_read = at25_ee_read;
at25->nvmem_config.reg_write = at25_ee_write;
at25->nvmem_config.priv = at25;
@@ -366,17 +419,34 @@ static int at25_probe(struct spi_device *spi)
at25->nvmem_config.word_size = 1;
at25->nvmem_config.size = chip.byte_len;
- at25->nvmem = devm_nvmem_register(&spi->dev, &at25->nvmem_config);
- if (IS_ERR(at25->nvmem))
- return PTR_ERR(at25->nvmem);
+ at25->nvmem = devm_nvmem_register(&spimem->spi->dev,
+ &at25->nvmem_config);
+ if (IS_ERR(at25->nvmem)) {
+ err = PTR_ERR(at25->nvmem);
+ goto err_free_scratchbuf;
+ }
- dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n",
+ dev_info(&spimem->spi->dev, "%d %s %s eeprom%s, pagesize %u\n",
(chip.byte_len < 1024) ? chip.byte_len : (chip.byte_len / 1024),
(chip.byte_len < 1024) ? "Byte" : "KByte",
at25->chip.name,
(chip.flags & EE_READONLY) ? " (readonly)" : "",
at25->chip.page_size);
return 0;
+
+err_free_scratchbuf:
+ kfree(at25->scratchbuf);
+
+ return err;
+}
+
+static int at25_remove(struct spi_mem *spimem)
+{
+ struct at25_data *at25 = spi_mem_get_drvdata(spimem);
+
+ kfree(at25->scratchbuf);
+
+ return 0;
}
/*-------------------------------------------------------------------------*/
@@ -387,15 +457,15 @@ static const struct of_device_id at25_of_match[] = {
};
MODULE_DEVICE_TABLE(of, at25_of_match);
-static struct spi_driver at25_driver = {
- .driver = {
- .name = "at25",
+static struct spi_mem_driver at25_driver = {
+ .spidrv.driver = {
+ .name = "at25",
.of_match_table = at25_of_match,
},
- .probe = at25_probe,
+ .probe = at25_probe,
+ .remove = at25_remove,
};
-
-module_spi_driver(at25_driver);
+module_spi_mem_driver(at25_driver);
MODULE_DESCRIPTION("Driver for most SPI EEPROMs");
MODULE_AUTHOR("David Brownell");
--
2.20.1