[v2] spi: add Renesas RPC-IF driver
diff mbox series

Message ID 6515c5ec-8432-0b20-426d-0428bbdf3712@cogentembedded.com
State New, archived
Headers show
Series
  • [v2] spi: add Renesas RPC-IF driver
Related show

Commit Message

Sergei Shtylyov Jan. 15, 2020, 8:46 p.m. UTC
Add the SPI driver for the Renesas RPC-IF.  It's the "front end" driver
using the "back end" APIs in the main driver to talk to the real hardware.
We only implement the 'spi-mem' interface -- there's no need to implemebt
the usual SPI driver methods...

Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>.

Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>

---
The patch is against the 'for-next' branch of Mark Brown's 'spi.git' repo.
It depends on the memory RPC-IF driver (posted in December) in order to
build/work:

https://patchwork.kernel.org/patch/11283125/
https://patchwork.kernel.org/patch/11283127/

The known issue with writing to the JFFS2 file system (updates not surviving
unmount/remount) were inherited from the original driver by Mason Yang...

Changes in version 2:
- removed unneeded transfer_one_message() method and the related code;
- fixed up #include's as we switch from MFD to the memory core driver;
- removed unneeded #include <linux/pm_runtime.h>;
- removed 'struct rpcif_spi', replacing it with 'struct rpcif' everywhere;
- added spi_mem_default_supports_op() call to rpcif_spi_mem_supports_op();
- added rpcif_sw_init() call in rpcif_spi_probe();
- set SPI_CONTROLLER_HALF_DUPLEX flag in rpcif_spi_probe();
- added a new variable 'struct device *parent' and renamed the 'ret' variable
  to 'error' in rpcif_spi_probe();
- changed the order of calls in the error path of rpcif_spi_probe() and in
  rpcif_spi_remove();
- changed from 'select' to 'depends on' the main driver Kconfig symbol,
  removed 'depends on ARCH_RENESAS || COMPILE TEST';
- renamed rpcif_spi_mem_set_prep_op_cfg() to rpcif_spi_mem_prepare(), updated
  the rpcif_io_xfer() call there to match the RPC-IF core driver, changed
  'rpc_op' there from parameter into the local variable;
- changed the platform driver's name to "rpc-if-spi";
- fixed whitespace in rpcif_spi_mem_exec_op()'s prototype; 
- beautified the whitespace in the initializers of 'rpcif_spi_mem_ops' and
  'rpcif_spi_driver';
- changed the heading comment from /* */ to //;
- updated the patch description with more details.

 drivers/spi/Kconfig      |    6 +
 drivers/spi/Makefile     |    1 
 drivers/spi/spi-rpc-if.c |  213 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+)

Comments

Chris Brandt Feb. 7, 2020, 10:36 p.m. UTC | #1
On Wed, Jan 15, 2020, Sergei Shtylyov wrote:
> Add the SPI driver for the Renesas RPC-IF.  It's the "front end" driver
> using the "back end" APIs in the main driver to talk to the real hardware.
> We only implement the 'spi-mem' interface -- there's no need to implemebt
> the usual SPI driver methods...

I tried these patches on an RZ/A1H RSK board.

At first, things were looking good. It would probe the SPI flash correctly
and I could read out data.

But, when I went to try and do an erase, it all went bad.
Looking at the actual SPI lines, the commands coming out don't look like what
I would expect from an MTD device.

For example, I do a 
  $ flash_eraseall /dev/mtd3
and all that comes out are Read Status commands (0x05).
All the write enables and erase commands are missing.

So, it looks like any command that is a write-only never actually sends
anything.

I did try and do a page program command:
  $ echo hello >  /dev/mtd3
It sent the page program command (0x12), but in this case, it still didn't work
because a write enable command was not sent first.
I assume the MTD layer is requesting the correct sequence of commands, but
Somehow this new driver is dropping of them.

Chris
Chris Brandt Feb. 10, 2020, 6:06 p.m. UTC | #2
Hi Sergei,

As I mentioned, when testing on RZ/A1, some commands are not transmitted.

Basically and SPI command that did not have 'data' payload:
	0x06: Write Enable
	0xDC: Erase 256 kB (4-byte address)
	0x04: Write Disable

The reason seems to be there is no case to set rpc->dir when there is no data.
So, it just gets left at whatever the last transfer was.

I added this fix and now it seems to work fine.

diff --git a/drivers/spi/spi-rpc-if.c b/drivers/spi/spi-rpc-if.c
index 0ff1a538bbd5..2165a0761844 100644
--- a/drivers/spi/spi-rpc-if.c
+++ b/drivers/spi/spi-rpc-if.c
@@ -53,6 +54,9 @@ static void rpcif_spi_mem_prepare(struct spi_device *spi_dev,
                default:
                        break;
                }
+       } else {
+               rpc_op.data.dir = RPCIF_NO_DATA;
+               rpc->dir = RPCIF_NO_DATA;
        }
 
        rpcif_prepare(rpc, &rpc_op, offs, len);


This seems like a bug that would effect add devices, not just the RZ/A1.



Side note, erase seems OK...but writing data seems to get messed up.
As you can see below, it's adding 2 bytes of 00 into the write stream.

$ flash_eraseall /dev/mtd3
Erasing 256 Kibyte @ 1000000 - 100% complete.
$ hexdump -C -n100 /dev/mtd3
00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000060
$ hexdump -C -n100 /dev/mtd3
00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000060
$ echo "hello" > /dev/mtd3
$ hexdump -C -n100 /dev/mtd3
00000000  68 65 6c 6c 00 00 6f 0a  ff ff ff ff ff ff ff ff  |hell..o.........|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000060


Chris
Sergei Shtylyov Feb. 10, 2020, 6:43 p.m. UTC | #3
Hello!

On 02/08/2020 01:36 AM, Chris Brandt wrote:

>> Add the SPI driver for the Renesas RPC-IF.  It's the "front end" driver
>> using the "back end" APIs in the main driver to talk to the real hardware.
>> We only implement the 'spi-mem' interface -- there's no need to implemebt
>> the usual SPI driver methods...
> 
> I tried these patches on an RZ/A1H RSK board.
> 
> At first, things were looking good. It would probe the SPI flash correctly
> and I could read out data.
> 
> But, when I went to try and do an erase, it all went bad.
> Looking at the actual SPI lines, the commands coming out don't look like what
> I would expect from an MTD device.
> 
> For example, I do a 
>   $ flash_eraseall /dev/mtd3
> and all that comes out are Read Status commands (0x05).
> All the write enables and erase commands are missing.

   Well, I have warned that writes don't work.

> So, it looks like any command that is a write-only never actually sends
> anything.
> 
> I did try and do a page program command:
>   $ echo hello >  /dev/mtd3
> It sent the page program command (0x12), but in this case, it still didn't work
> because a write enable command was not sent first.
> I assume the MTD layer is requesting the correct sequence of commands, but
> Somehow this new driver is dropping of them.

   Would explain what I saw (writes not surviving a JFFS2 remount)...

> Chris

MBR, Sergei
Chris Brandt Feb. 10, 2020, 7:34 p.m. UTC | #4
Hello Sergei,

On Feb 10, 2020, Chris Brandt wrote:
> Side note, erase seems OK...but writing data seems to get messed up.
> As you can see below, it's adding 2 bytes of 00 into the write stream.
> 
> $ flash_eraseall /dev/mtd3
> Erasing 256 Kibyte @ 1000000 - 100% complete.
> $ hexdump -C -n100 /dev/mtd3
> 00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff
> |................|
> *
> 00000060
> $ hexdump -C -n100 /dev/mtd3
> 00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff
> |................|
> *
> 00000060
> $ echo "hello" > /dev/mtd3
> $ hexdump -C -n100 /dev/mtd3
> 00000000  68 65 6c 6c 00 00 6f 0a  ff ff ff ff ff ff ff ff
> |hell..o.........|
> 00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff
> |................|
> *
> 00000060


I think here is the issue of the extra 00s in the write byte stream.

For your code, you have this in function rpcif_io_xfer():

			memcpy(data, rpc->buffer + pos, nbytes);
			if (nbytes > 4) {
				regmap_write(rpc->regmap, RPCIF_SMWDR1,
					     data[0]);
				regmap_write(rpc->regmap, RPCIF_SMWDR0,
					     data[1]);
			} else if (nbytes > 2) {
				regmap_write(rpc->regmap, RPCIF_SMWDR0,
					     data[0]);
			} else	{
				regmap_write(rpc->regmap, RPCIF_SMWDR0,
					     data[0] << 16);
			}


But, you cannot do a 32-bit register write when you have less than 32-bits of data.

If you only have 2 bytes of data left to write, you have to do a 16-bit write to the SMWDR0 register.
If you only have 1 byte of data left to write, you have to do a 8-bit write to the SMWDR0 register.

If you only have 3 bytes of data left to write, you first send 2 bytes, then send the last byte.

Your regmap is only set up to do 32-bit writes, so you'll have to use something like iowrite16 and iowrite8.
This is why I did not use regmap in my SPI-BSC driver.

For example, here is the code from my SPI-BSC driver:

	while (len > 0) {
		if (len >= 4) {
			unit = 4;
			smenr = SMENR_SPIDE(SPIDE_32BITS);
		} else {
			unit = len;
			if (unit == 3)
				unit = 2;

			if (unit >= 2)
				smenr = SMENR_SPIDE(SPIDE_16BITS);
			else
				smenr = SMENR_SPIDE(SPIDE_8BITS);
		}

		/* set 4bytes data, bit stream */
		smwdr0 = *data++;
		if (unit >= 2)
			smwdr0 |= (u32)(*data++ << 8);
		if (unit >= 3)
			smwdr0 |= (u32)(*data++ << 16);
		if (unit >= 4)
			smwdr0 |= (u32)(*data++ << 24);

		/* mask unwrite area */
		if (unit == 3)
			smwdr0 |= 0xFF000000;
		else if (unit == 2)
			smwdr0 |= 0xFFFF0000;
		else if (unit == 1)
			smwdr0 |= 0xFFFFFF00;

		/* write send data. */
		if (unit == 2)
			spibsc_write16(sbsc, SMWDR0, (u16)smwdr0);
		else if (unit == 1)
			spibsc_write8(sbsc, SMWDR0, (u8)smwdr0);
		else
			spibsc_write(sbsc, SMWDR0, smwdr0);


Chris

Patch
diff mbox series

Index: spi/drivers/spi/Kconfig
===================================================================
--- spi.orig/drivers/spi/Kconfig
+++ spi/drivers/spi/Kconfig
@@ -578,6 +578,12 @@  config SPI_RB4XX
 	help
 	  SPI controller driver for the Mikrotik RB4xx series boards.
 
+config SPI_RPCIF
+	tristate "Renesas RPC-IF SPI driver"
+	depends on RENESAS_RPCIF
+	help
+	  SPI driver for Renesas R-Car Gen3 RPC-IF.
+
 config SPI_RSPI
 	tristate "Renesas RSPI/QSPI controller"
 	depends on SUPERH || ARCH_RENESAS || COMPILE_TEST
Index: spi/drivers/spi/Makefile
===================================================================
--- spi.orig/drivers/spi/Makefile
+++ spi/drivers/spi/Makefile
@@ -87,6 +87,7 @@  obj-$(CONFIG_SPI_QCOM_QSPI)		+= spi-qcom
 obj-$(CONFIG_SPI_QUP)			+= spi-qup.o
 obj-$(CONFIG_SPI_ROCKCHIP)		+= spi-rockchip.o
 obj-$(CONFIG_SPI_RB4XX)			+= spi-rb4xx.o
+obj-$(CONFIG_SPI_RPCIF)			+= spi-rpc-if.o
 obj-$(CONFIG_SPI_RSPI)			+= spi-rspi.o
 obj-$(CONFIG_SPI_S3C24XX)		+= spi-s3c24xx-hw.o
 spi-s3c24xx-hw-y			:= spi-s3c24xx.o
Index: spi/drivers/spi/spi-rpc-if.c
===================================================================
--- /dev/null
+++ spi/drivers/spi/spi-rpc-if.c
@@ -0,0 +1,213 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// RPC-IF SPI/QSPI/Octa driver
+//
+// Copyright (C) 2018 ~ 2019 Renesas Solutions Corp.
+// Copyright (C) 2019 Macronix International Co., Ltd.
+// Copyright (C) 2019 Cogent Embedded, Inc.
+//
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+#include <memory/renesas-rpc-if.h>
+
+#include <asm/unaligned.h>
+
+static void rpcif_spi_mem_prepare(struct spi_device *spi_dev,
+				  const struct spi_mem_op *spi_op,
+				  u64 *offs, size_t *len)
+{
+	struct rpcif *rpc = spi_controller_get_devdata(spi_dev->controller);
+	struct rpcif_op rpc_op = { };
+
+	rpc_op.cmd.opcode = spi_op->cmd.opcode;
+	rpc_op.cmd.buswidth = spi_op->cmd.buswidth;
+
+	if (spi_op->addr.nbytes) {
+		rpc_op.addr.buswidth = spi_op->addr.buswidth;
+		rpc_op.addr.nbytes = spi_op->addr.nbytes;
+		rpc_op.addr.val = spi_op->addr.val;
+	}
+
+	if (spi_op->dummy.nbytes) {
+		rpc_op.dummy.buswidth = spi_op->dummy.buswidth;
+		rpc_op.dummy.ncycles  = spi_op->dummy.nbytes * 8 /
+					spi_op->dummy.buswidth;
+	}
+
+	if (spi_op->data.nbytes || (offs && len)) {
+		rpc_op.data.buswidth = spi_op->data.buswidth;
+		rpc_op.data.nbytes = spi_op->data.nbytes;
+		switch (spi_op->data.dir) {
+		case SPI_MEM_DATA_IN:
+			rpc_op.data.dir = RPCIF_DATA_IN;
+			rpc_op.data.buf.in = spi_op->data.buf.in;
+			break;
+		case SPI_MEM_DATA_OUT:
+			rpc_op.data.dir = RPCIF_DATA_OUT;
+			rpc_op.data.buf.out = spi_op->data.buf.out;
+			break;
+		default:
+			break;
+		}
+	}
+
+	rpcif_prepare(rpc, &rpc_op, offs, len);
+}
+
+static bool rpcif_spi_mem_supports_op(struct spi_mem *mem,
+				      const struct spi_mem_op *op)
+{
+	if (!spi_mem_default_supports_op(mem, op))
+		return false;
+
+	if (op->data.buswidth > 4 || op->addr.buswidth > 4 ||
+	    op->dummy.buswidth > 4 || op->cmd.buswidth > 4 ||
+	    op->addr.nbytes > 4)
+		return false;
+
+	return true;
+}
+
+static ssize_t rpcif_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
+					 u64 offs, size_t len, void *buf)
+{
+	struct rpcif *rpc =
+		spi_controller_get_devdata(desc->mem->spi->controller);
+
+	if (offs + desc->info.offset + len > U32_MAX)
+		return -EINVAL;
+
+	rpcif_spi_mem_prepare(desc->mem->spi, &desc->info.op_tmpl, &offs, &len);
+
+	return rpcif_dirmap_read(rpc, offs, len, buf);
+}
+
+static int rpcif_spi_mem_dirmap_create(struct spi_mem_dirmap_desc *desc)
+{
+	struct rpcif *rpc =
+		spi_controller_get_devdata(desc->mem->spi->controller);
+
+	if (desc->info.offset + desc->info.length > U32_MAX)
+		return -ENOTSUPP;
+
+	if (!rpcif_spi_mem_supports_op(desc->mem, &desc->info.op_tmpl))
+		return -ENOTSUPP;
+
+	if (!rpc->dirmap && desc->info.op_tmpl.data.dir == SPI_MEM_DATA_IN)
+		return -ENOTSUPP;
+
+	if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT)
+		return -ENOTSUPP;
+
+	return 0;
+}
+
+static int rpcif_spi_mem_exec_op(struct spi_mem *mem,
+				 const struct spi_mem_op *op)
+{
+	struct rpcif *rpc =
+		spi_controller_get_devdata(mem->spi->controller);
+
+	rpcif_spi_mem_prepare(mem->spi, op, NULL, NULL);
+
+	return rpcif_io_xfer(rpc);
+}
+
+static const struct spi_controller_mem_ops rpcif_spi_mem_ops = {
+	.supports_op	= rpcif_spi_mem_supports_op,
+	.exec_op	= rpcif_spi_mem_exec_op,
+	.dirmap_create	= rpcif_spi_mem_dirmap_create,
+	.dirmap_read	= rpcif_spi_mem_dirmap_read,
+};
+
+static int rpcif_spi_probe(struct platform_device *pdev)
+{
+	struct device *parent = pdev->dev.parent;
+	struct spi_controller *ctlr;
+	struct rpcif *rpc;
+	int error;
+
+	ctlr = spi_alloc_master(&pdev->dev, sizeof(*rpc));
+	if (!ctlr)
+		return -ENOMEM;
+
+	rpc = spi_controller_get_devdata(ctlr);
+	rpcif_sw_init(rpc, parent);
+
+	platform_set_drvdata(pdev, ctlr);
+
+	ctlr->dev.of_node = parent->of_node;
+
+	rpcif_enable_rpm(rpc);
+
+	ctlr->num_chipselect = 1;
+	ctlr->mem_ops = &rpcif_spi_mem_ops;
+
+	ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
+	ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_QUAD | SPI_RX_QUAD;
+	ctlr->flags = SPI_CONTROLLER_HALF_DUPLEX;
+
+	rpcif_hw_init(rpc, false);
+
+	error = spi_register_controller(ctlr);
+	if (error) {
+		dev_err(&pdev->dev, "spi_register_controller failed\n");
+		goto err_put_ctlr;
+	}
+	return 0;
+
+err_put_ctlr:
+	rpcif_disable_rpm(rpc);
+	spi_controller_put(ctlr);
+
+	return error;
+}
+
+static int rpcif_spi_remove(struct platform_device *pdev)
+{
+	struct spi_controller *ctlr = platform_get_drvdata(pdev);
+	struct rpcif *rpc = spi_controller_get_devdata(ctlr);
+
+	spi_unregister_controller(ctlr);
+	rpcif_disable_rpm(rpc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int rpcif_spi_suspend(struct device *dev)
+{
+	struct spi_controller *ctlr = dev_get_drvdata(dev);
+
+	return spi_controller_suspend(ctlr);
+}
+
+static int rpcif_spi_resume(struct device *dev)
+{
+	struct spi_controller *ctlr = dev_get_drvdata(dev);
+
+	return spi_controller_resume(ctlr);
+}
+
+static SIMPLE_DEV_PM_OPS(rpcif_spi_pm_ops, rpcif_spi_suspend, rpcif_spi_resume);
+#define DEV_PM_OPS	(&rpcif_spi_pm_ops)
+#else
+#define DEV_PM_OPS	NULL
+#endif
+
+static struct platform_driver rpcif_spi_driver = {
+	.probe	= rpcif_spi_probe,
+	.remove	= rpcif_spi_remove,
+	.driver = {
+		.name	= "rpc-if-spi",
+		.pm	= DEV_PM_OPS,
+	},
+};
+module_platform_driver(rpcif_spi_driver);
+
+MODULE_DESCRIPTION("Renesas RPC-IF SPI driver");
+MODULE_LICENSE("GPL v2");