[v7,1/2] spi: Add Renesas R-Car Gen3 RPC-IF SPI controller driver
diff mbox series

Message ID 1548227352-14910-2-git-send-email-masonccyang@mxic.com.tw
State Under Review
Delegated to: Geert Uytterhoeven
Headers show
Series
  • spi: Add Renesas R-Car Gen3 RPC-IF SPI driver
Related show

Commit Message

Mason Yang Jan. 23, 2019, 7:09 a.m. UTC
Add a driver for Renesas R-Car Gen3 RPC-IF SPI controller.

Signed-off-by: Mason Yang <masonccyang@mxic.com.tw>
Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
---
 drivers/spi/Kconfig           |   6 +
 drivers/spi/Makefile          |   1 +
 drivers/spi/spi-renesas-rpc.c | 805 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 812 insertions(+)
 create mode 100644 drivers/spi/spi-renesas-rpc.c

Comments

Sergei Shtylyov Jan. 23, 2019, 6:04 p.m. UTC | #1
Hello!

On 01/23/2019 10:09 AM, Mason Yang wrote:

> Add a driver for Renesas R-Car Gen3 RPC-IF SPI controller.
> 
> Signed-off-by: Mason Yang <masonccyang@mxic.com.tw>
> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
[...]
> diff --git a/drivers/spi/spi-renesas-rpc.c b/drivers/spi/spi-renesas-rpc.c
> new file mode 100644
> index 0000000..0127f25
> --- /dev/null
> +++ b/drivers/spi/spi-renesas-rpc.c
> @@ -0,0 +1,805 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Copyright (C) 2018 ~ 2019 Renesas Solutions Corp.
> +// Copyright (C) 2018 Macronix International Co., Ltd.
> +//
> +// R-Car Gen3 RPC-IF SPI/QSPI/Octa driver

   Do we really support Octa?

> +//
> +// Authors:
> +//	Mason Yang <masonccyang@mxic.com.tw>
> +//
[...]
> +#include <asm/unaligned.h>
[...]
> +	} else if (rx_buf) {
> +		//
> +		// RPC-IF spoils the data for the commands without an address
> +		// phase (like RDID) in the manual mode, so we'll have to work
> +		// around this issue by using the external address space read
> +		// mode instead.
> +		//
> +		if (!(smenr & RPC_SMENR_ADE(0xf))) {
> +			regmap_update_bits(rpc->regmap, RPC_CMNCR,
> +					   RPC_CMNCR_MD, 0);
> +			regmap_write(rpc->regmap, RPC_DRCR,
> +				     RPC_DRCR_RBURST(32) | RPC_DRCR_RBE);
> +			regmap_write(rpc->regmap, RPC_DREAR, RPC_DREAR_EAC(1));
> +			regmap_write(rpc->regmap, RPC_DRCMR, rpc->cmd);
> +			regmap_write(rpc->regmap, RPC_DRDMCR, rpc->dummy);
> +			regmap_write(rpc->regmap, RPC_DROPR, 0);
> +			regmap_write(rpc->regmap, RPC_DRENR, smenr);
> +			memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen);

   Just noticed: if rpc->dirmap is NULL (which is now allowed), we have a kernel oops
here. :-(

[...]
> +static int rpc_spi_probe(struct platform_device *pdev)
> +{
> +	struct spi_controller *ctlr;
> +	struct resource *res;
> +	struct rpc_spi *rpc;
> +	struct device_node *rpc_if;

   Don't think that's a good name. Why not call it 'flash'?

> +	int ret;
> +
> +	rpc_if = of_get_next_child(pdev->dev.of_node, NULL);
> +	if (!rpc_if) {
> +		dev_warn(&pdev->dev, "no spi-nor device node found\n");

   How about "no flash node found"?

> +		return -ENODEV;
> +	}
> +
> +	ret = of_device_is_compatible(rpc_if, "jedec,spi-nor");
> +	if (!ret) {
> +		dev_warn(&pdev->dev, "no compatible spi-nor device found\n");

   "no SPI-NOR device found"?

[...]
> +	ret = spi_register_controller(ctlr);
> +	if (ret) {
> +		dev_err(&pdev->dev, "spi_register_controller failed\n");
> +		goto err_put_ctlr;
> +	}
> +	return 0;
> +
> +err_put_ctlr:
> +	spi_controller_put(ctlr);
> +	pm_runtime_disable(&pdev->dev);
> +
> +	return ret;
> +}
[...]
> +static const struct of_device_id rpc_spi_of_ids[] = {
> +	{ .compatible = "renesas,r8a77995-rpc", },

   Why (if it has no differences with the generic gen3 value below)?
Please remove.

> +	{ .compatible = "renesas,rcar-gen3-rpc", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, rpc_spi_of_ids);
[...]

MBR, Sergei
Marek Vasut Jan. 24, 2019, 1:51 a.m. UTC | #2
On 1/23/19 8:09 AM, Mason Yang wrote:
> Add a driver for Renesas R-Car Gen3 RPC-IF SPI controller.
> 
> Signed-off-by: Mason Yang <masonccyang@mxic.com.tw>
> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>

[...]

> +#define RPC_CMNCR		0x0000	// R/W

Is there any reason for using those horrible C++ comments ?

[...]

> +module_platform_driver(rpc_spi_driver);

RPC is not a SPI controller, it's a SPI and HF controller.

Also, how difficult will it be to add the HF support ?

> +MODULE_AUTHOR("Mason Yang <masonccyang@mxic.com.tw>");
> +MODULE_DESCRIPTION("Renesas R-Car Gen3 RPC-IF SPI controller driver");
> +MODULE_LICENSE("GPL v2");
>
Marek Vasut Jan. 24, 2019, 3:13 a.m. UTC | #3
On 1/24/19 3:23 AM, masonccyang@mxic.com.tw wrote:
> Hi Marek,

Hi,

>> "Marek Vasut" <marek.vasut@gmail.com>
>> 2019/01/24 上午 09:54
>>
>>
>> > +#define RPC_CMNCR      0x0000   // R/W
>>
>> Is there any reason for using those horrible C++ comments ?
> 
> By Mark's comments for the SPDX header needs to be C++ style and
> I patch the whole RPC driver comments using C++ style otherwise it looks
> messy.

I think the C++ comments should only be applied to the SPDX identifier,
maybe the header, but not the entire file.

>> [...]
>>
>> > +module_platform_driver(rpc_spi_driver);
>>
>> RPC is not a SPI controller, it's a SPI and HF controller.
>>
>> Also, how difficult will it be to add the HF support ?
> 
> One of my customers needs RPC SPI driver for our company's
> Octal-Flash,MX25UW51245G.
> We don't have HF product and hope you could understanding.

I am worried that when we need to add RPC HF support (which is what all
boards but the D3 Draak use), we will have to rewrite the entire driver
and/or convert it to MFD and that would be a tremendous undertaking. I'd
prefer to have the driver ready for the HF addition before it's accepted
upstream.
Geert Uytterhoeven Jan. 24, 2019, 9:12 a.m. UTC | #4
Hi Mason,

On Thu, Jan 24, 2019 at 3:07 AM <masonccyang@mxic.com.tw> wrote:
> > "Sergei Shtylyov" <sergei.shtylyov@cogentembedded.com>
> > 2019/01/24 上午 02:04

> > > +static const struct of_device_id rpc_spi_of_ids[] = {
> > > +   { .compatible = "renesas,r8a77995-rpc", },
> >
> >    Why (if it has no differences with the generic gen3 value below)?
> > Please remove.
>
> By Geert's comments in dts,
>
>          rpc: rpc@ee200000 {
>                 compatible = "renesas,r8a7795-rpc", "renesas,rcar-gen3-rpc";
>                 reg = <0 0xee200000 0 0x200>, <0 0x08000000 0 0x4000000>,
>                       <0 0xee208000 0 0x100>;
>
> I modified it to "renesas,r8a77995-rpc" because this is Soc I developed and validated.

There's a difference between compatible values in DT and in drivers:
  - DT should list all compatible values that are applicable, from
    most-specific to least-specific,
  - Drivers should match against the least-specific compatible value that
    is sufficient to get the job done.
    In this case, that's "renesas,rcar-gen3-rpc".

The driver will probably have to be extended to match against
"renesas,r8a77970-rpc" when R-Car V3M support is added, as RPC on V3M
has an extra register that must be set, and thus cannot be declared
compatible with "renesas,rcar-gen3-rpc".

I hope this explanation makes it clearer.

Gr{oetje,eeting}s,

                        Geert
Sergei Shtylyov Jan. 24, 2019, 11:27 a.m. UTC | #5
On 01/24/2019 05:06 AM, masonccyang@mxic.com.tw wrote:

>> > +// SPDX-License-Identifier: GPL-2.0
>> > +//
>> > +// Copyright (C) 2018 ~ 2019 Renesas Solutions Corp.
>> > +// Copyright (C) 2018 Macronix International Co., Ltd.
>> > +//
>> > +// R-Car Gen3 RPC-IF SPI/QSPI/Octa driver
>>
>>    Do we really support Octa?
> 
> yes and it needs to patch spi-nor layer and RPC driver for sure.

   It?

>> > +//
>> > +// Authors:
>> > +//   Mason Yang <masonccyang@mxic.com.tw>
>> > +//
>> [...]
>> > +#include <asm/unaligned.h>
>> [...]
>> > +   } else if (rx_buf) {
>> > +      //
>> > +      // RPC-IF spoils the data for the commands without an address
>> > +      // phase (like RDID) in the manual mode, so we'll have to work
>> > +      // around this issue by using the external address space read
>> > +      // mode instead.
>> > +      //
>> > +      if (!(smenr & RPC_SMENR_ADE(0xf))) {
>> > +         regmap_update_bits(rpc->regmap, RPC_CMNCR,
>> > +                  RPC_CMNCR_MD, 0);
>> > +         regmap_write(rpc->regmap, RPC_DRCR,
>> > +                 RPC_DRCR_RBURST(32) | RPC_DRCR_RBE);
>> > +         regmap_write(rpc->regmap, RPC_DREAR, RPC_DREAR_EAC(1));
>> > +         regmap_write(rpc->regmap, RPC_DRCMR, rpc->cmd);
>> > +         regmap_write(rpc->regmap, RPC_DRDMCR, rpc->dummy);
>> > +         regmap_write(rpc->regmap, RPC_DROPR, 0);
>> > +         regmap_write(rpc->regmap, RPC_DRENR, smenr);
>> > +         memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen);
>>
>>    Just noticed: if rpc->dirmap is NULL (which is now allowed), we
>> have a kernel oops
>> here. :-(
> 
> Do you patch your spi-mem.c ?

   What do yuo mean? The only extra patch I have to apply now to renesas.git repo's devel
branch is "mtd: devices: m25p80: Use the spi-mem dirmap API"...

> both dirmap_create() in your rpc driver and spi-mem layer should handle it well.

   I just saw that you don't fail the driver's probe() method if devm_ioremap_resource()
fails for the "dirmap" and "wbuf" resources...

>> [...]
>> > +static const struct of_device_id rpc_spi_of_ids[] = {
>> > +   { .compatible = "renesas,r8a77995-rpc", },
>>
>>    Why (if it has no differences with the generic gen3 value below)?
>> Please remove.
> 
> By Geert's comments in dts,
> 
>          rpc: rpc@ee200000 {
>                 compatible = "renesas,r8a7795-rpc", "renesas,rcar-gen3-rpc";
>                 reg = <0 0xee200000 0 0x200>, <0 0x08000000 0 0x4000000>,
>                       <0 0xee208000 0 0x100>;
> 
> I modified it to "renesas,r8a77995-rpc" because this is Soc I developed and validated.

  Hopefully this matter is cleared up by Geert... :-)

> thanks,
> Mason

MBR, Sergei
Sergei Shtylyov Jan. 24, 2019, 11:27 a.m. UTC | #6
On 01/24/2019 05:06 AM, masonccyang@mxic.com.tw wrote:

>> > +// SPDX-License-Identifier: GPL-2.0
>> > +//
>> > +// Copyright (C) 2018 ~ 2019 Renesas Solutions Corp.
>> > +// Copyright (C) 2018 Macronix International Co., Ltd.
>> > +//
>> > +// R-Car Gen3 RPC-IF SPI/QSPI/Octa driver
>>
>>    Do we really support Octa?
> 
> yes and it needs to patch spi-nor layer and RPC driver for sure.

   It?

>> > +//
>> > +// Authors:
>> > +//   Mason Yang <masonccyang@mxic.com.tw>
>> > +//
>> [...]
>> > +#include <asm/unaligned.h>
>> [...]
>> > +   } else if (rx_buf) {
>> > +      //
>> > +      // RPC-IF spoils the data for the commands without an address
>> > +      // phase (like RDID) in the manual mode, so we'll have to work
>> > +      // around this issue by using the external address space read
>> > +      // mode instead.
>> > +      //
>> > +      if (!(smenr & RPC_SMENR_ADE(0xf))) {
>> > +         regmap_update_bits(rpc->regmap, RPC_CMNCR,
>> > +                  RPC_CMNCR_MD, 0);
>> > +         regmap_write(rpc->regmap, RPC_DRCR,
>> > +                 RPC_DRCR_RBURST(32) | RPC_DRCR_RBE);
>> > +         regmap_write(rpc->regmap, RPC_DREAR, RPC_DREAR_EAC(1));
>> > +         regmap_write(rpc->regmap, RPC_DRCMR, rpc->cmd);
>> > +         regmap_write(rpc->regmap, RPC_DRDMCR, rpc->dummy);
>> > +         regmap_write(rpc->regmap, RPC_DROPR, 0);
>> > +         regmap_write(rpc->regmap, RPC_DRENR, smenr);
>> > +         memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen);
>>
>>    Just noticed: if rpc->dirmap is NULL (which is now allowed), we
>> have a kernel oops
>> here. :-(
> 
> Do you patch your spi-mem.c ?

   What do you mean? The only extra patch I have to apply now to renesas.git repo's devel
branch is "mtd: devices: m25p80: Use the spi-mem dirmap API"...

> both dirmap_create() in your rpc driver and spi-mem layer should handle it well.

   I just saw that you don't fail the driver's probe() method if devm_ioremap_resource()
fails for the "dirmap" and "wbuf" resources...

>> [...]
>> > +static const struct of_device_id rpc_spi_of_ids[] = {
>> > +   { .compatible = "renesas,r8a77995-rpc", },
>>
>>    Why (if it has no differences with the generic gen3 value below)?
>> Please remove.
> 
> By Geert's comments in dts,
> 
>          rpc: rpc@ee200000 {
>                 compatible = "renesas,r8a7795-rpc", "renesas,rcar-gen3-rpc";
>                 reg = <0 0xee200000 0 0x200>, <0 0x08000000 0 0x4000000>,
>                       <0 0xee208000 0 0x100>;
> 
> I modified it to "renesas,r8a77995-rpc" because this is Soc I developed and validated.

  Hopefully this matter is cleared up by Geert... :-)

> thanks,
> Mason

MBR, Sergei
Sergei Shtylyov Jan. 24, 2019, 11:31 a.m. UTC | #7
On 01/24/2019 05:23 AM, masonccyang@mxic.com.tw wrote:

>> [...]
>>
>> > +module_platform_driver(rpc_spi_driver);
>>
>> RPC is not a SPI controller, it's a SPI and HF controller.
>>
>> Also, how difficult will it be to add the HF support ?
> 
> One of my customers needs RPC SPI driver for our company's Octal-Flash,MX25UW51245G.
> We don't have HF product and hope you could understanding.

   I'm looking into the HF support (we have a driver internally).

> 
> thanks & best regards,
> Mason

MBR, Sergei
Marek Vasut Jan. 26, 2019, 9:41 a.m. UTC | #8
On 1/24/19 7:28 AM, masonccyang@mxic.com.tw wrote:
> Hi Marek,

Hi,

>> "Marek Vasut" <marek.vasut@gmail.com>
>> 2019/01/24 上午 11:14
>> >>
>> >> > +module_platform_driver(rpc_spi_driver);
>> >>
>> >> RPC is not a SPI controller, it's a SPI and HF controller.
>> >>
>> >> Also, how difficult will it be to add the HF support ?
>> >
>> > One of my customers needs RPC SPI driver for our company's
>> > Octal-Flash,MX25UW51245G.
>> > We don't have HF product and hope you could understanding.
>>
>> I am worried that when we need to add RPC HF support (which is what all
>> boards but the D3 Draak use), we will have to rewrite the entire driver
>> and/or convert it to MFD and that would be a tremendous undertaking. I'd
>> prefer to have the driver ready for the HF addition before it's accepted
>> upstream.
>>
> 
> I think maybe your concerned would be happened only if HF driver goes with
> spi-mem layer.
> 
> A comment for HF from Daniel Fishman. FYR.
> 
> https://www.quora.com/What-is-a-hyper-flash-memory-and-how-is-it-different-from-normal-flash-memory

I have a decent idea what HF and SPI NOR are, since I wrote the RPC
driver for both HF and SPI mode for U-Boot (as I mentioned earlier).

The HF in Linux would use the CFI NOR part of MTD framework. My concern
is that when we need to add HF support into this driver, this driver
will have to be basically rewritten, since the architecture won't allow
for that. I'd like to avoid that, since the majority of Gen3 boards,
expect for the D3 Draak, use RPC in HF mode.
Marek Vasut Jan. 28, 2019, 11:03 a.m. UTC | #9
On 1/28/19 2:38 AM, masonccyang@mxic.com.tw wrote:
> Hi Marek,

Hi,

>> "Marek Vasut" <marek.vasut@gmail.com>
>> >> >> > +module_platform_driver(rpc_spi_driver);
>> >> >>
>> >> >> RPC is not a SPI controller, it's a SPI and HF controller.
>> >> >>
>> >> >> Also, how difficult will it be to add the HF support ?
>> >> >
>> >> > One of my customers needs RPC SPI driver for our company's
>> >> > Octal-Flash,MX25UW51245G.
>> >> > We don't have HF product and hope you could understanding.
>> >>
>> >> I am worried that when we need to add RPC HF support (which is what all
>> >> boards but the D3 Draak use), we will have to rewrite the entire driver
>> >> and/or convert it to MFD and that would be a tremendous
> undertaking. I'd
>> >> prefer to have the driver ready for the HF addition before it's
> accepted
>> >> upstream.
>> >>
>> >
>> > I think maybe your concerned would be happened only if HF driver
> goes with
>> > spi-mem layer.
>> >
>> > A comment for HF from Daniel Fishman. FYR.
>> >
>> > https://www.quora.com/What-is-a-hyper-flash-memory-and-how-is-it-
>> different-from-normal-flash-memory
>>
>> I have a decent idea what HF and SPI NOR are, since I wrote the RPC
>> driver for both HF and SPI mode for U-Boot (as I mentioned earlier).
>>
>> The HF in Linux would use the CFI NOR part of MTD framework. My concern
>> is that when we need to add HF support into this driver, this driver
>> will have to be basically rewritten, since the architecture won't allow
>> for that. I'd like to avoid that, since the majority of Gen3 boards,
>> expect for the D3 Draak, use RPC in HF mode.
> 
> FYI~
> 
> MX25UW51245g(64MByte Octa)                      S26KL512S(64MByte HF)
>    8 IO                                                  8 IO
> 200MHz DDR@1.8v                                   166MHz DDR@1.8v
> 
> support Read-while-write                       Not support
> good for OTA,etc
> powerful application

What does that mean ?
Marek Vasut Jan. 29, 2019, 4:43 a.m. UTC | #10
On 1/29/19 3:26 AM, masonccyang@mxic.com.tw wrote:
> Hi Marek,

Hi,

>> >> "Marek Vasut" <marek.vasut@gmail.com>
>> >> >> >> > +module_platform_driver(rpc_spi_driver);
>> >> >> >>
>> >> >> >> RPC is not a SPI controller, it's a SPI and HF controller.
>> >> >> >>
>> >> >> >> Also, how difficult will it be to add the HF support ?
>> >> >> >
>> >> >> > One of my customers needs RPC SPI driver for our company's
>> >> >> > Octal-Flash,MX25UW51245G.
>> >> >> > We don't have HF product and hope you could understanding.
>> >> >>
>> >> >> I am worried that when we need to add RPC HF support (which is
> what all
>> >> >> boards but the D3 Draak use), we will have to rewrite the entire
> driver
>> >> >> and/or convert it to MFD and that would be a tremendous
>> > undertaking. I'd
>> >> >> prefer to have the driver ready for the HF addition before it's
>> > accepted
>> >> >> upstream.
>> >> >>
>> >> >
>> >> > I think maybe your concerned would be happened only if HF driver
>> > goes with
>> >> > spi-mem layer.
>> >> >
>> >> > A comment for HF from Daniel Fishman. FYR.
>> >> >
>> >> > https://www.quora.com/What-is-a-hyper-flash-memory-and-how-is-it-
>> >> different-from-normal-flash-memory
>> >>
>> >> I have a decent idea what HF and SPI NOR are, since I wrote the RPC
>> >> driver for both HF and SPI mode for U-Boot (as I mentioned earlier).
>> >>
>> >> The HF in Linux would use the CFI NOR part of MTD framework. My concern
>> >> is that when we need to add HF support into this driver, this driver
>> >> will have to be basically rewritten, since the architecture won't allow
>> >> for that. I'd like to avoid that, since the majority of Gen3 boards,
>> >> expect for the D3 Draak, use RPC in HF mode.
>> >
>> > FYI~
>> >
>> > MX25UW51245g(64MByte Octa)                      S26KL512S(64MByte HF)
>> >    8 IO                                                  8 IO
>> > 200MHz DDR@1.8v                                   166MHz DDR@1.8v
>> >
>> > support Read-while-write                       Not support
>> > good for OTA,etc
>> > powerful application
>>
>> What does that mean ?
> 
> I have no idea why would you say "since the majority of Gen3 boards use
> RPC in HF mode" ?

Well, the H3/M3W/M3N S-X(S) and the H3/M3 ULCB and E3 Ebisu all boot
from HF. Only the D3 Draak uses QSPI NOR.

> So far as I know that HF is provided by Cypress only and
> any mass production product use the component which is provided by only
> one provider
> will be a big risk.
> 
> Compare to HF, there are more provider of SPI/Octa could support the
> mass production product
> as their second provider.
> 
> In addition, from the technical points of view, mx25uw51245g is more
> powerful than HF and
> good for complicate user application, i.e., OTA and so on.

Did you consider protocol overhead too ? I don't think you can compare
them just by raw numbers of pins and bus frequency.

Note that over-the-air update (if that's what you mean by OTA) is
completely separate from the underlying storage device.

> I think customer have more choice for their flash memory component.
Note that none of this is really relevant to my concerns above regarding
HF support. This driver should be implemented as MFD driver and the SPI
part should use the MFD core part , just like the future HF part.
Marek Vasut Jan. 30, 2019, 7:15 a.m. UTC | #11
On 1/30/19 3:22 AM, masonccyang@mxic.com.tw wrote:
> Hi Marek,

Hi,

>> "Marek Vasut" <marek.vasut@gmail.com>
>> 2019/01/29 下午 12:45
>>
>> To
>>
>> masonccyang@mxic.com.tw,
>>
>> cc
>>
>> bbrezillon@kernel.org, broonie@kernel.org, "Geert Uytterhoeven"
>> <geert+renesas@glider.be>, "Simon Horman" <horms@verge.net.au>,
>> juliensu@mxic.com.tw, linux-kernel@vger.kernel.org, linux-renesas-
>> soc@vger.kernel.org, linux-spi@vger.kernel.org,
>> sergei.shtylyov@cogentembedded.com, zhengxunli@mxic.com.tw
>>
>> Subject
>>
>> Re: [PATCH v7 1/2] spi: Add Renesas R-Car Gen3 RPC-IF SPI controller
> driver
>>
>> On 1/29/19 3:26 AM, masonccyang@mxic.com.tw wrote:
>> > Hi Marek,
>>
>> Hi,
>>
>> >> >> "Marek Vasut" <marek.vasut@gmail.com>
>> >> >> >> >> > +module_platform_driver(rpc_spi_driver);
>> >> >> >> >>
>> >> >> >> >> RPC is not a SPI controller, it's a SPI and HF controller.
>> >> >> >> >>
>> >> >> >> >> Also, how difficult will it be to add the HF support ?
>> >> >> >> >
>> >> >> >> > One of my customers needs RPC SPI driver for our company's
>> >> >> >> > Octal-Flash,MX25UW51245G.
>> >> >> >> > We don't have HF product and hope you could understanding.
>> >> >> >>
>> >> >> >> I am worried that when we need to add RPC HF support (which is
>> > what all
>> >> >> >> boards but the D3 Draak use), we will have to rewrite the entire
>> > driver
>> >> >> >> and/or convert it to MFD and that would be a tremendous
>> >> > undertaking. I'd
>> >> >> >> prefer to have the driver ready for the HF addition before it's
>> >> > accepted
>> >> >> >> upstream.
>> >> >> >>
>> >> >> >
>> >> >> > I think maybe your concerned would be happened only if HF driver
>> >> > goes with
>> >> >> > spi-mem layer.
>> >> >> >
>> >> >> > A comment for HF from Daniel Fishman. FYR.
>> >> >> >
>> >> >> > https://www.quora.com/What-is-a-hyper-flash-memory-and-how-is-it-
>> >> >> different-from-normal-flash-memory
>> >> >>
>> >> >> I have a decent idea what HF and SPI NOR are, since I wrote the RPC
>> >> >> driver for both HF and SPI mode for U-Boot (as I mentioned earlier).
>> >> >>
>> >> >> The HF in Linux would use the CFI NOR part of MTD framework. My
> concern
>> >> >> is that when we need to add HF support into this driver, this driver
>> >> >> will have to be basically rewritten, since the architecture
> won't allow
>> >> >> for that. I'd like to avoid that, since the majority of Gen3 boards,
>> >> >> expect for the D3 Draak, use RPC in HF mode.
>> >> >
>> >> > FYI~
>> >> >
>> >> > MX25UW51245g(64MByte Octa)                      S26KL512S(64MByte HF)
>> >> >    8 IO                                                  8 IO
>> >> > 200MHz DDR@1.8v                                   166MHz DDR@1.8v
>> >> >
>> >> > support Read-while-write                       Not support
>> >> > good for OTA,etc
>> >> > powerful application
>> >>
>> >> What does that mean ?
>> >
>> > I have no idea why would you say "since the majority of Gen3 boards use
>> > RPC in HF mode" ?
>>
>> Well, the H3/M3W/M3N S-X(S) and the H3/M3 ULCB and E3 Ebisu all boot
>> from HF. Only the D3 Draak uses QSPI NOR.
> 
> It's understandable because mx25uw51245g is a new product and it has been
> adopted by Renesas’ Automotive Instrument Cluster RH850/D1M1A MCU.

The aforementioned boards are not going away however. There's too many
users to ignore those.

> We also have patched R-Car's BL for booting from Octa-Flash as bellow log:
> 
> NOTICE:  BL2: R-Car D3 Initial Program Loader(CA53) Rev.0.5.1
> NOTICE:  BL2: PRR is R-Car D3 Ver1.0
> NOTICE:  BL2: Boot device is MXIC_OctaFlash
> NOTICE:  BL2: LCM state is CM
> NOTICE:  BL2: DDR3L-1866(rev.0.02)
> NOTICE:  BL2: QoS is default setting(rev.0.07)
> NOTICE:  BL2: v1.3(release):
> NOTICE:  BL2: Built : 09:56:31, Sep 26 2018
> NOTICE:  BL2: Normal boot
> NOTICE:  BL2: dst=0xe63111f0 src=0x8180000 len=512(0x200)
> NOTICE:  BL2: dst=0x43f00000 src=0x8180400 len=6144(0x1800)
> NOTICE:  BL2: dst=0x44000000 src=0x81c0000 len=65536(0x10000)
> NOTICE:  BL2: dst=0x50000000 src=0x8640000 len=1048576(0x100000)

This looks like a very old ATF version. Anyway, please submit those
patches upstream, they should be generic.

>> > So far as I know that HF is provided by Cypress only and
>> > any mass production product use the component which is provided by only
>> > one provider
>> > will be a big risk.
>> >
>> > Compare to HF, there are more provider of SPI/Octa could support the
>> > mass production product
>> > as their second provider.
>> >
>> > In addition, from the technical points of view, mx25uw51245g is more
>> > powerful than HF and
>> > good for complicate user application, i.e., OTA and so on.
>>
>> Did you consider protocol overhead too ? I don't think you can compare
>> them just by raw numbers of pins and bus frequency.
>>
>> Note that over-the-air update (if that's what you mean by OTA) is
>> completely separate from the underlying storage device.
> 
> It's key feature of mx25uw51245g supports Read-while-Write capability
> that allows read access from one memory bank while writing to another
> memory bank.

Note that this sales pitch is rather off-topic.

>> > I think customer have more choice for their flash memory component.
>> Note that none of this is really relevant to my concerns above regarding
>> HF support. This driver should be implemented as MFD driver and the SPI
>> part should use the MFD core part , just like the future HF part.
>>
> 
> Even though they are both internal NOR memory chip, is it a trend of Kernel
> for the different HW controller and protocol ?

I'm not sure I understand the question, but the HF behaves like a CFI
NOR, that's why it should be operated via the CFI part of the MTD
framework. The SPI NOR behaves, well, like a SPI NOR and so should be
operated via the SPI NOR part of the MTD framework.

However, a lot of the code operating the RPC is common for the HF and
SPI NOR modes, which is why that part should be a MFD driver or some
common core driver.

> What about others,i.e., raw-NAND and spi-NAND ?

I don't think the RPC supports those, does it ?

> oh, a little bit leaving the topic !
> anyway, nice to talk to you about this R-Car Gen3 RPC driver.
[...]
Boris Brezillon Jan. 30, 2019, 9:26 a.m. UTC | #12
On Wed, 30 Jan 2019 08:15:25 +0100
Marek Vasut <marek.vasut@gmail.com> wrote:

> >> > So far as I know that HF is provided by Cypress only and
> >> > any mass production product use the component which is provided by only
> >> > one provider
> >> > will be a big risk.
> >> >
> >> > Compare to HF, there are more provider of SPI/Octa could support the
> >> > mass production product
> >> > as their second provider.
> >> >
> >> > In addition, from the technical points of view, mx25uw51245g is more
> >> > powerful than HF and
> >> > good for complicate user application, i.e., OTA and so on.  
> >>
> >> Did you consider protocol overhead too ? I don't think you can compare
> >> them just by raw numbers of pins and bus frequency.
> >>
> >> Note that over-the-air update (if that's what you mean by OTA) is
> >> completely separate from the underlying storage device.  
> > 
> > It's key feature of mx25uw51245g supports Read-while-Write capability
> > that allows read access from one memory bank while writing to another
> > memory bank.  
> 
> Note that this sales pitch is rather off-topic.
> 

Fully agree with Marek on that point. The discussion is about
supporting both HF and SPI-MEM modes (or, at least have a plan to
support HF at some point) not figuring out which option is better.

Patch
diff mbox series

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 9f89cb1..6ad1782 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -544,6 +544,12 @@  config SPI_RSPI
 	help
 	  SPI driver for Renesas RSPI and QSPI blocks.
 
+config SPI_RENESAS_RPC
+	tristate "Renesas R-Car Gen3 RPC-IF controller"
+	depends on ARCH_RENESAS || COMPILE_TEST
+	help
+	  SPI driver for Renesas R-Car Gen3 RPC-IF.
+
 config SPI_QCOM_QSPI
 	tristate "QTI QSPI controller"
 	depends on ARCH_QCOM
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index f296270..9150732 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -84,6 +84,7 @@  obj-$(CONFIG_SPI_QUP)			+= spi-qup.o
 obj-$(CONFIG_SPI_ROCKCHIP)		+= spi-rockchip.o
 obj-$(CONFIG_SPI_RB4XX)			+= spi-rb4xx.o
 obj-$(CONFIG_SPI_RSPI)			+= spi-rspi.o
+obj-$(CONFIG_SPI_RENESAS_RPC)		+= spi-renesas-rpc.o
 obj-$(CONFIG_SPI_S3C24XX)		+= spi-s3c24xx-hw.o
 spi-s3c24xx-hw-y			:= spi-s3c24xx.o
 spi-s3c24xx-hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi-s3c24xx-fiq.o
diff --git a/drivers/spi/spi-renesas-rpc.c b/drivers/spi/spi-renesas-rpc.c
new file mode 100644
index 0000000..0127f25
--- /dev/null
+++ b/drivers/spi/spi-renesas-rpc.c
@@ -0,0 +1,805 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 ~ 2019 Renesas Solutions Corp.
+// Copyright (C) 2018 Macronix International Co., Ltd.
+//
+// R-Car Gen3 RPC-IF SPI/QSPI/Octa driver
+//
+// Authors:
+//	Mason Yang <masonccyang@mxic.com.tw>
+//
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+#include <asm/unaligned.h>
+
+#define RPC_CMNCR		0x0000	// R/W
+#define RPC_CMNCR_MD		BIT(31)
+#define RPC_CMNCR_SFDE		BIT(24) // undocumented bit but must be set
+#define RPC_CMNCR_MOIIO3(val)	(((val) & 0x3) << 22)
+#define RPC_CMNCR_MOIIO2(val)	(((val) & 0x3) << 20)
+#define RPC_CMNCR_MOIIO1(val)	(((val) & 0x3) << 18)
+#define RPC_CMNCR_MOIIO0(val)	(((val) & 0x3) << 16)
+#define RPC_CMNCR_MOIIO_HIZ	(RPC_CMNCR_MOIIO0(3) | RPC_CMNCR_MOIIO1(3) | \
+				 RPC_CMNCR_MOIIO2(3) | RPC_CMNCR_MOIIO3(3))
+#define RPC_CMNCR_IO3FV(val)	(((val) & 0x3) << 14) // undocumented
+#define RPC_CMNCR_IO2FV(val)	(((val) & 0x3) << 12) // undocumented
+#define RPC_CMNCR_IO0FV(val)	(((val) & 0x3) << 8)
+#define RPC_CMNCR_IOFV_HIZ	(RPC_CMNCR_IO0FV(3) | RPC_CMNCR_IO2FV(3) | \
+				 RPC_CMNCR_IO3FV(3))
+#define RPC_CMNCR_BSZ(val)	(((val) & 0x3) << 0)
+
+#define RPC_SSLDR		0x0004	// R/W
+#define RPC_SSLDR_SPNDL(d)	(((d) & 0x7) << 16)
+#define RPC_SSLDR_SLNDL(d)	(((d) & 0x7) << 8)
+#define RPC_SSLDR_SCKDL(d)	(((d) & 0x7) << 0)
+
+#define RPC_DRCR		0x000C	// R/W
+#define RPC_DRCR_SSLN		BIT(24)
+#define RPC_DRCR_RBURST(v)	((((v) - 1) & 0x1F) << 16)
+#define RPC_DRCR_RCF		BIT(9)
+#define RPC_DRCR_RBE		BIT(8)
+#define RPC_DRCR_SSLE		BIT(0)
+
+#define RPC_DRCMR		0x0010	// R/W
+#define RPC_DRCMR_CMD(c)	(((c) & 0xFF) << 16)
+#define RPC_DRCMR_OCMD(c)	(((c) & 0xFF) << 0)
+
+#define RPC_DREAR		0x0014	// R/W
+#define RPC_DREAR_EAC(c)	(((c) & 0x7) << 0)
+
+#define RPC_DROPR		0x0018	// R/W
+
+#define RPC_DRENR		0x001C	// R/W
+#define RPC_DRENR_CDB(o)	(u32)((((o) & 0x3) << 30))
+#define RPC_DRENR_OCDB(o)	(((o) & 0x3) << 28)
+#define RPC_DRENR_ADB(o)	(((o) & 0x3) << 24)
+#define RPC_DRENR_OPDB(o)	(((o) & 0x3) << 20)
+#define RPC_DRENR_DRDB(o)	(((o) & 0x3) << 16)
+#define RPC_DRENR_DME		BIT(15)
+#define RPC_DRENR_CDE		BIT(14)
+#define RPC_DRENR_OCDE		BIT(12)
+#define RPC_DRENR_ADE(v)	(((v) & 0xF) << 8)
+#define RPC_DRENR_OPDE(v)	(((v) & 0xF) << 4)
+
+#define RPC_SMCR		0x0020	// R/W
+#define RPC_SMCR_SSLKP		BIT(8)
+#define RPC_SMCR_SPIRE		BIT(2)
+#define RPC_SMCR_SPIWE		BIT(1)
+#define RPC_SMCR_SPIE		BIT(0)
+
+#define RPC_SMCMR		0x0024	// R/W
+#define RPC_SMCMR_CMD(c)	(((c) & 0xFF) << 16)
+#define RPC_SMCMR_OCMD(c)	(((c) & 0xFF) << 0)
+
+#define RPC_SMADR		0x0028	// R/W
+#define RPC_SMOPR		0x002C	// R/W
+#define RPC_SMOPR_OPD3(o)	(((o) & 0xFF) << 24)
+#define RPC_SMOPR_OPD2(o)	(((o) & 0xFF) << 16)
+#define RPC_SMOPR_OPD1(o)	(((o) & 0xFF) << 8)
+#define RPC_SMOPR_OPD0(o)	(((o) & 0xFF) << 0)
+
+#define RPC_SMENR		0x0030	// R/W
+#define RPC_SMENR_CDB(o)	(((o) & 0x3) << 30)
+#define RPC_SMENR_OCDB(o)	(((o) & 0x3) << 28)
+#define RPC_SMENR_ADB(o)	(((o) & 0x3) << 24)
+#define RPC_SMENR_OPDB(o)	(((o) & 0x3) << 20)
+#define RPC_SMENR_SPIDB(o)	(((o) & 0x3) << 16)
+#define RPC_SMENR_DME		BIT(15)
+#define RPC_SMENR_CDE		BIT(14)
+#define RPC_SMENR_OCDE		BIT(12)
+#define RPC_SMENR_ADE(v)	(((v) & 0xF) << 8)
+#define RPC_SMENR_OPDE(v)	(((v) & 0xF) << 4)
+#define RPC_SMENR_SPIDE(v)	(((v) & 0xF) << 0)
+
+#define RPC_SMRDR0		0x0038	// R
+#define RPC_SMRDR1		0x003C	// R
+#define RPC_SMWDR0		0x0040	// W
+#define RPC_SMWDR1		0x0044	// W
+
+#define RPC_CMNSR		0x0048	// R
+#define RPC_CMNSR_SSLF		BIT(1)
+#define RPC_CMNSR_TEND		BIT(0)
+
+#define RPC_DRDMCR		0x0058	// R/W
+#define RPC_DRDRENR		0x005C	// R/W
+
+#define RPC_SMDMCR		0x0060	// R/W
+#define RPC_SMDMCR_DMCYC(v)	((((v) - 1) & 0x1F) << 0)
+
+#define RPC_SMDRENR		0x0064	// R/W
+#define RPC_SMDRENR_HYPE	(0x7 << 12)
+#define RPC_SMDRENR_ADDRE	BIT(8)
+#define RPC_SMDRENR_OPDRE	BIT(4)
+#define RPC_SMDRENR_SPIDRE	BIT(0)
+
+#define RPC_PHYCNT		0x007C	// R/W
+#define RPC_PHYCNT_CAL		BIT(31)
+#define PRC_PHYCNT_OCTA_AA	BIT(22)
+#define PRC_PHYCNT_OCTA_SA	BIT(23)
+#define PRC_PHYCNT_EXDS		BIT(21)
+#define RPC_PHYCNT_OCT		BIT(20)
+#define RPC_PHYCNT_STRTIM(v)	(((v) & 0x7) << 15)
+#define RPC_PHYCNT_WBUF2	BIT(4)
+#define RPC_PHYCNT_WBUF		BIT(2)
+#define RPC_PHYCNT_PHYMEM(v)	(((v) & 0x3) << 0)
+
+#define RPC_PHYOFFSET1		0x0080	// R/W
+#define RPC_PHYOFFSET1_DDRTMG(v) (((v) & 0x3) << 28)
+#define RPC_PHYOFFSET2		0x0084	// R/W
+#define RPC_PHYOFFSET2_OCTTMG(v) (((v) & 0x7) << 8)
+
+#define RPC_WBUF_SIZE		256	// Write Buffer size
+
+struct rpc_spi {
+	struct clk *clk_rpc;
+	void __iomem *base;
+	void __iomem *dirmap;
+	void __iomem *wbuf;
+	struct regmap *regmap;
+	u32 cur_speed_hz;
+	u32 cmd;
+	u32 addr;
+	u32 dummy;
+	u32 smcr;
+	u32 smenr;
+	u32 xferlen;
+	u32 totalxferlen;
+	enum spi_mem_data_dir xfer_dir;
+	struct reset_control *rstc;
+};
+
+static int rpc_spi_set_freq(struct rpc_spi *rpc, unsigned long freq)
+{
+	int ret;
+
+	if (rpc->cur_speed_hz == freq)
+		return 0;
+
+	ret = clk_set_rate(rpc->clk_rpc, freq);
+	if (ret)
+		return ret;
+
+	rpc->cur_speed_hz = freq;
+	return ret;
+}
+
+static void rpc_spi_hw_init(struct rpc_spi *rpc)
+{
+	//
+	// NOTE: The 0x260 are undocumented bits, but they must be set.
+	//	RPC_PHYCNT_STRTIM is strobe timing adjustment bit,
+	//	0x0 : the delay is biggest,
+	//	0x1 : the delay is 2nd biggest,
+	//	On H3 ES1.x, the value should be 0, while on others,
+	//	the value should be 6.
+	//
+	regmap_write(rpc->regmap, RPC_PHYCNT, RPC_PHYCNT_CAL |
+				  RPC_PHYCNT_STRTIM(6) | 0x260);
+
+	//
+	// NOTE: The 0x1511144 are undocumented bits, but they must be set
+	//       for RPC_PHYOFFSET1.
+	//	 The 0x31 are undocumented bits, but they must be set
+	//	 for RPC_PHYOFFSET2.
+	//
+	regmap_write(rpc->regmap, RPC_PHYOFFSET1, RPC_PHYOFFSET1_DDRTMG(3) |
+		     0x1511144);
+	regmap_write(rpc->regmap, RPC_PHYOFFSET2, 0x31 |
+		     RPC_PHYOFFSET2_OCTTMG(4));
+	regmap_write(rpc->regmap, RPC_SSLDR, RPC_SSLDR_SPNDL(7) |
+		     RPC_SSLDR_SLNDL(7) | RPC_SSLDR_SCKDL(7));
+	regmap_write(rpc->regmap, RPC_CMNCR, RPC_CMNCR_MD | RPC_CMNCR_SFDE |
+		     RPC_CMNCR_MOIIO_HIZ | RPC_CMNCR_IOFV_HIZ |
+		     RPC_CMNCR_BSZ(0));
+}
+
+static int wait_msg_xfer_end(struct rpc_spi *rpc)
+{
+	u32 sts;
+
+	return regmap_read_poll_timeout(rpc->regmap, RPC_CMNSR, sts,
+					sts & RPC_CMNSR_TEND, 0, USEC_PER_SEC);
+}
+
+static u8 rpc_bits_set(u32 nbytes)
+{
+	nbytes = clamp(nbytes, 1U, 4U);
+
+	return GENMASK(3, 4 - nbytes);
+}
+
+static int rpc_spi_io_xfer(struct rpc_spi *rpc,
+			   const void *tx_buf, void *rx_buf)
+{
+	u32 smenr, smcr, data, pos = 0;
+	int ret;
+
+	regmap_update_bits(rpc->regmap, RPC_CMNCR, RPC_CMNCR_MD, RPC_CMNCR_MD);
+	regmap_write(rpc->regmap, RPC_SMDRENR, 0);
+	regmap_write(rpc->regmap, RPC_SMCMR, rpc->cmd);
+	regmap_write(rpc->regmap, RPC_SMDMCR, rpc->dummy);
+	regmap_write(rpc->regmap, RPC_SMADR, rpc->addr);
+	smenr = rpc->smenr;
+
+	if (tx_buf) {
+		while (pos < rpc->xferlen) {
+			u32 nbytes = rpc->xferlen - pos;
+
+			regmap_write(rpc->regmap, RPC_SMWDR0,
+				     get_unaligned((u32 *)(tx_buf + pos)));
+
+			smcr = rpc->smcr | RPC_SMCR_SPIE;
+
+			if (nbytes > 4) {
+				nbytes = 4;
+				smcr |= RPC_SMCR_SSLKP;
+			}
+
+			regmap_write(rpc->regmap, RPC_SMENR, smenr);
+			regmap_write(rpc->regmap, RPC_SMCR, smcr);
+			ret = wait_msg_xfer_end(rpc);
+			if (ret)
+				goto err_out;
+
+			pos += nbytes;
+			smenr = rpc->smenr & ~RPC_SMENR_CDE &
+					     ~RPC_SMENR_ADE(0xf);
+		}
+	} else if (rx_buf) {
+		//
+		// RPC-IF spoils the data for the commands without an address
+		// phase (like RDID) in the manual mode, so we'll have to work
+		// around this issue by using the external address space read
+		// mode instead.
+		//
+		if (!(smenr & RPC_SMENR_ADE(0xf))) {
+			regmap_update_bits(rpc->regmap, RPC_CMNCR,
+					   RPC_CMNCR_MD, 0);
+			regmap_write(rpc->regmap, RPC_DRCR,
+				     RPC_DRCR_RBURST(32) | RPC_DRCR_RBE);
+			regmap_write(rpc->regmap, RPC_DREAR, RPC_DREAR_EAC(1));
+			regmap_write(rpc->regmap, RPC_DRCMR, rpc->cmd);
+			regmap_write(rpc->regmap, RPC_DRDMCR, rpc->dummy);
+			regmap_write(rpc->regmap, RPC_DROPR, 0);
+			regmap_write(rpc->regmap, RPC_DRENR, smenr);
+			memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen);
+			regmap_write(rpc->regmap, RPC_DRCR, RPC_DRCR_RCF);
+		} else {
+			while (pos < rpc->xferlen) {
+				u32 nbytes = rpc->xferlen - pos;
+
+				if (nbytes > 4)
+					nbytes = 4;
+
+				regmap_write(rpc->regmap, RPC_SMENR, smenr);
+				regmap_write(rpc->regmap, RPC_SMCR, rpc->smcr |
+					     RPC_SMCR_SPIE);
+				ret = wait_msg_xfer_end(rpc);
+				if (ret)
+					goto err_out;
+
+				regmap_read(rpc->regmap, RPC_SMRDR0, &data);
+				memcpy(rx_buf + pos, &data, nbytes);
+				pos += nbytes;
+
+				regmap_write(rpc->regmap, RPC_SMADR,
+					     rpc->addr + pos);
+			}
+		}
+	} else {
+		regmap_write(rpc->regmap, RPC_SMENR, rpc->smenr);
+		regmap_write(rpc->regmap, RPC_SMCR, rpc->smcr | RPC_SMCR_SPIE);
+		ret = wait_msg_xfer_end(rpc);
+		if (ret)
+			goto err_out;
+	}
+
+	return 0;
+
+err_out:
+	return reset_control_reset(rpc->rstc);
+}
+
+static void rpc_spi_mem_set_prep_op_cfg(struct spi_device *spi,
+					const struct spi_mem_op *op,
+					u64 *offs, size_t *len)
+{
+	struct rpc_spi *rpc = spi_controller_get_devdata(spi->controller);
+
+	rpc->cmd = RPC_SMCMR_CMD(op->cmd.opcode);
+	rpc->smenr = RPC_SMENR_CDE |
+		     RPC_SMENR_CDB(ilog2(op->cmd.buswidth));
+	rpc->totalxferlen = 1;
+	rpc->xfer_dir = SPI_MEM_NO_DATA;
+	rpc->xferlen = 0;
+	rpc->addr = 0;
+
+	if (op->addr.nbytes) {
+		rpc->smenr |= RPC_SMENR_ADB(ilog2(op->addr.buswidth));
+		if (op->addr.nbytes == 4)
+			rpc->smenr |= RPC_SMENR_ADE(0xf);
+		else
+			rpc->smenr |= RPC_SMENR_ADE(0x7);
+
+		if (offs && len)
+			rpc->addr = *offs;
+		else
+			rpc->addr = op->addr.val;
+		rpc->totalxferlen += op->addr.nbytes;
+	}
+
+	if (op->dummy.nbytes) {
+		rpc->smenr |= RPC_SMENR_DME;
+		rpc->dummy = RPC_SMDMCR_DMCYC(op->dummy.nbytes);
+		rpc->totalxferlen += op->dummy.nbytes;
+	}
+
+	if (op->data.nbytes || (offs && len)) {
+		switch (op->data.dir) {
+		case SPI_MEM_DATA_IN:
+			rpc->smcr = RPC_SMCR_SPIRE;
+			rpc->xfer_dir = SPI_MEM_DATA_IN;
+			break;
+		case SPI_MEM_DATA_OUT:
+			rpc->smcr = RPC_SMCR_SPIWE;
+			rpc->xfer_dir = SPI_MEM_DATA_OUT;
+			break;
+		default:
+			break;
+		}
+
+		if (offs && len) {
+			rpc->smenr |= RPC_SMENR_SPIDE(rpc_bits_set(*len)) |
+				      RPC_SMENR_SPIDB(ilog2(op->data.buswidth));
+			rpc->xferlen = *len;
+			rpc->totalxferlen += *len;
+		} else {
+			rpc->smenr |=
+				RPC_SMENR_SPIDE(rpc_bits_set(op->data.nbytes)) |
+				RPC_SMENR_SPIDB(ilog2(op->data.buswidth));
+			rpc->xferlen = op->data.nbytes;
+			rpc->totalxferlen += op->data.nbytes;
+		}
+	}
+}
+
+static bool rpc_spi_mem_supports_op(struct spi_mem *mem,
+				    const struct spi_mem_op *op)
+{
+	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 rpc_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc,
+				       u64 offs, size_t len, void *buf)
+{
+	struct rpc_spi *rpc =
+			spi_controller_get_devdata(desc->mem->spi->controller);
+	int ret;
+
+	if (offs + desc->info.offset + len > U32_MAX)
+		return -EINVAL;
+
+	if (len > 0x4000000)
+		len = 0x4000000;
+
+	ret = rpc_spi_set_freq(rpc, desc->mem->spi->max_speed_hz);
+	if (ret)
+		return ret;
+
+	rpc_spi_mem_set_prep_op_cfg(desc->mem->spi,
+				    &desc->info.op_tmpl, &offs, &len);
+
+	regmap_update_bits(rpc->regmap, RPC_CMNCR, RPC_CMNCR_MD, 0);
+	regmap_write(rpc->regmap, RPC_DRCR, RPC_DRCR_RBURST(32) |
+		     RPC_DRCR_RBE);
+
+	regmap_write(rpc->regmap, RPC_DRCMR, rpc->cmd);
+	regmap_write(rpc->regmap, RPC_DREAR, RPC_DREAR_EAC(1));
+	regmap_write(rpc->regmap, RPC_DROPR, 0);
+	regmap_write(rpc->regmap, RPC_DRENR, rpc->smenr);
+	regmap_write(rpc->regmap, RPC_DRDMCR, rpc->dummy);
+	regmap_write(rpc->regmap, RPC_DRDRENR, 0);
+
+	memcpy_fromio(buf, rpc->dirmap + desc->info.offset + offs, len);
+
+	return len;
+}
+
+static ssize_t rpc_spi_mem_dirmap_write(struct spi_mem_dirmap_desc *desc,
+					u64 offs, size_t len, const void *buf)
+{
+	struct rpc_spi *rpc =
+			spi_controller_get_devdata(desc->mem->spi->controller);
+	int ret;
+
+	if (offs + desc->info.offset + len > U32_MAX)
+		return -EINVAL;
+
+	if (len > RPC_WBUF_SIZE)
+		len = RPC_WBUF_SIZE;
+
+	ret = rpc_spi_set_freq(rpc, desc->mem->spi->max_speed_hz);
+	if (ret)
+		return ret;
+
+	rpc_spi_mem_set_prep_op_cfg(desc->mem->spi,
+				    &desc->info.op_tmpl, &offs, &len);
+
+	regmap_update_bits(rpc->regmap, RPC_CMNCR, RPC_CMNCR_MD, RPC_CMNCR_MD);
+
+	regmap_write(rpc->regmap, RPC_SMDRENR, 0);
+
+	regmap_update_bits(rpc->regmap, RPC_PHYCNT, RPC_PHYCNT_STRTIM(7) |
+			   RPC_PHYCNT_WBUF2 | RPC_PHYCNT_WBUF,
+			   RPC_PHYCNT_WBUF2 | RPC_PHYCNT_WBUF);
+
+	memcpy_toio(rpc->wbuf, buf, len);
+
+	regmap_write(rpc->regmap, RPC_SMCMR, rpc->cmd);
+	regmap_write(rpc->regmap, RPC_SMADR, offs);
+	regmap_write(rpc->regmap, RPC_SMENR, rpc->smenr);
+	regmap_write(rpc->regmap, RPC_SMCR, rpc->smcr | RPC_SMCR_SPIE);
+	ret = wait_msg_xfer_end(rpc);
+	if (ret)
+		goto err_out;
+
+	regmap_write(rpc->regmap, RPC_DRCR, RPC_DRCR_RCF);
+
+	regmap_update_bits(rpc->regmap, RPC_PHYCNT, RPC_PHYCNT_STRTIM(7) |
+			   RPC_PHYCNT_WBUF2 | RPC_PHYCNT_WBUF,
+			   RPC_PHYCNT_STRTIM(6));
+
+	return len;
+
+err_out:
+	return reset_control_reset(rpc->rstc);
+}
+
+static int rpc_spi_mem_dirmap_create(struct spi_mem_dirmap_desc *desc)
+{
+	struct rpc_spi *rpc =
+			spi_controller_get_devdata(desc->mem->spi->controller);
+
+	if (desc->info.offset + desc->info.length > U32_MAX)
+		return -ENOTSUPP;
+
+	if (!rpc_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 (!rpc->wbuf &&
+	    desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT)
+		return -ENOTSUPP;
+
+	return 0;
+}
+
+static int rpc_spi_mem_exec_op(struct spi_mem *mem,
+			       const struct spi_mem_op *op)
+{
+	struct rpc_spi *rpc = spi_controller_get_devdata(mem->spi->controller);
+	int ret;
+
+	ret = rpc_spi_set_freq(rpc, mem->spi->max_speed_hz);
+	if (ret)
+		return ret;
+
+	rpc_spi_mem_set_prep_op_cfg(mem->spi, op, NULL, NULL);
+
+	ret = rpc_spi_io_xfer(rpc,
+			      op->data.dir == SPI_MEM_DATA_OUT ?
+			      op->data.buf.out : NULL,
+			      op->data.dir == SPI_MEM_DATA_IN ?
+			      op->data.buf.in : NULL);
+
+	return ret;
+}
+
+static const struct spi_controller_mem_ops rpc_spi_mem_ops = {
+	.supports_op = rpc_spi_mem_supports_op,
+	.exec_op = rpc_spi_mem_exec_op,
+	.dirmap_create = rpc_spi_mem_dirmap_create,
+	.dirmap_read = rpc_spi_mem_dirmap_read,
+	.dirmap_write = rpc_spi_mem_dirmap_write,
+};
+
+static void rpc_spi_transfer_setup(struct rpc_spi *rpc,
+				   struct spi_message *msg)
+{
+	struct spi_transfer *t, xfer[4] = { };
+	u32 i, xfercnt, xferpos = 0;
+
+	rpc->totalxferlen = 0;
+	rpc->xfer_dir = SPI_MEM_NO_DATA;
+
+	list_for_each_entry(t, &msg->transfers, transfer_list) {
+		if (t->tx_buf) {
+			xfer[xferpos].tx_buf = t->tx_buf;
+			xfer[xferpos].tx_nbits = t->tx_nbits;
+		}
+
+		if (t->rx_buf) {
+			xfer[xferpos].rx_buf = t->rx_buf;
+			xfer[xferpos].rx_nbits = t->rx_nbits;
+		}
+
+		if (t->len) {
+			xfer[xferpos++].len = t->len;
+			rpc->totalxferlen += t->len;
+		}
+
+		if (list_is_last(&t->transfer_list, &msg->transfers)) {
+			if (xferpos > 1) {
+				if (t->rx_buf) {
+					rpc->xfer_dir = SPI_MEM_DATA_IN;
+					rpc->smcr = RPC_SMCR_SPIRE;
+				} else if (t->tx_buf) {
+					rpc->xfer_dir = SPI_MEM_DATA_OUT;
+					rpc->smcr = RPC_SMCR_SPIWE;
+				}
+			}
+		}
+	}
+
+	xfercnt = xferpos;
+	rpc->xferlen = xfer[--xferpos].len;
+	rpc->cmd = RPC_SMCMR_CMD(((u8 *)xfer[0].tx_buf)[0]);
+	rpc->smenr = RPC_SMENR_CDE |
+		     RPC_SMENR_CDB(ilog2((unsigned int)xfer[0].tx_nbits));
+	rpc->addr = 0;
+
+	if (xfercnt > 2 && xfer[1].len && xfer[1].tx_buf) {
+		rpc->smenr |=
+			RPC_SMENR_ADB(ilog2((unsigned int)xfer[1].tx_nbits));
+
+		for (i = 0; i < xfer[1].len; i++)
+			rpc->addr |= ((u8 *)xfer[1].tx_buf)[i] <<
+				     (8 * (xfer[1].len - i - 1));
+
+		if (xfer[1].len == 4)
+			rpc->smenr |= RPC_SMENR_ADE(0xf);
+		else
+			rpc->smenr |= RPC_SMENR_ADE(0x7);
+	}
+
+	if (xfercnt > 3 && xfer[2].len && xfer[2].tx_buf) {
+		rpc->smenr |= RPC_SMENR_DME;
+		rpc->dummy = RPC_SMDMCR_DMCYC(xfer[2].len);
+	}
+
+	for (i = xfercnt - 1; i < xfercnt && xfercnt > 1; i++) {
+		if (xfer[i].rx_buf) {
+			rpc->smenr |=
+				RPC_SMENR_SPIDE(rpc_bits_set(xfer[i].len)) |
+				RPC_SMENR_SPIDB(ilog2
+					((unsigned int)xfer[i].rx_nbits));
+		} else if (xfer[i].tx_buf) {
+			rpc->smenr |=
+				RPC_SMENR_SPIDE(rpc_bits_set(xfer[i].len)) |
+				RPC_SMENR_SPIDB(ilog2
+					((unsigned int)xfer[i].tx_nbits));
+		}
+	}
+}
+
+static inline int rpc_spi_xfer_message(struct rpc_spi *rpc,
+				       struct spi_transfer *data_xfer)
+{
+	int ret;
+
+	ret = rpc_spi_set_freq(rpc, data_xfer->speed_hz);
+	if (ret)
+		return ret;
+
+	ret = rpc_spi_io_xfer(rpc,
+			      rpc->xfer_dir == SPI_MEM_DATA_OUT ?
+			      data_xfer->tx_buf : NULL,
+			      rpc->xfer_dir == SPI_MEM_DATA_IN ?
+			      data_xfer->rx_buf : NULL);
+
+	return ret;
+}
+
+static int rpc_spi_transfer_one_message(struct spi_controller *ctlr,
+					struct spi_message *msg)
+{
+	struct rpc_spi *rpc = spi_controller_get_devdata(ctlr);
+	struct spi_transfer *data_xfer;
+	int ret;
+
+	rpc_spi_transfer_setup(rpc, msg);
+
+	data_xfer = list_last_entry(&msg->transfers, struct spi_transfer,
+				    transfer_list);
+
+	ret = rpc_spi_xfer_message(rpc, data_xfer);
+	if (ret)
+		goto out;
+
+	msg->status = 0;
+	msg->actual_length = rpc->totalxferlen;
+out:
+	spi_finalize_current_message(ctlr);
+	return 0;
+}
+
+static const struct regmap_range rpc_spi_volatile_ranges[] = {
+	regmap_reg_range(RPC_SMRDR0, RPC_SMRDR0),
+	regmap_reg_range(RPC_SMWDR0, RPC_SMWDR0),
+	regmap_reg_range(RPC_CMNSR, RPC_CMNSR),
+};
+
+static const struct regmap_access_table rpc_spi_volatile_table = {
+	.yes_ranges	= rpc_spi_volatile_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(rpc_spi_volatile_ranges),
+};
+
+static const struct regmap_config rpc_spi_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.fast_io = true,
+	.max_register = RPC_PHYOFFSET2,
+	.volatile_table = &rpc_spi_volatile_table,
+};
+
+static int rpc_spi_probe(struct platform_device *pdev)
+{
+	struct spi_controller *ctlr;
+	struct resource *res;
+	struct rpc_spi *rpc;
+	struct device_node *rpc_if;
+	int ret;
+
+	rpc_if = of_get_next_child(pdev->dev.of_node, NULL);
+	if (!rpc_if) {
+		dev_warn(&pdev->dev, "no spi-nor device node found\n");
+		return -ENODEV;
+	}
+
+	ret = of_device_is_compatible(rpc_if, "jedec,spi-nor");
+	if (!ret) {
+		dev_warn(&pdev->dev, "no compatible spi-nor device found\n");
+		return -ENODEV;
+	}
+
+	ctlr = spi_alloc_master(&pdev->dev, sizeof(*rpc));
+	if (!ctlr)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, ctlr);
+
+	rpc = spi_controller_get_devdata(ctlr);
+
+	ctlr->dev.of_node = pdev->dev.of_node;
+
+	rpc->clk_rpc = devm_clk_get(&pdev->dev, "rpc");
+	if (IS_ERR(rpc->clk_rpc))
+		return PTR_ERR(rpc->clk_rpc);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+	rpc->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rpc->base))
+		return PTR_ERR(rpc->base);
+
+	rpc->regmap = devm_regmap_init_mmio(&pdev->dev, rpc->base,
+					    &rpc_spi_regmap_config);
+	if (IS_ERR(rpc->regmap)) {
+		dev_err(&pdev->dev,
+			"failed to init regmap for rpc-spi, error %ld\n",
+			PTR_ERR(rpc->regmap));
+		return PTR_ERR(rpc->regmap);
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dirmap");
+	rpc->dirmap = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rpc->dirmap))
+		rpc->dirmap = NULL;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wbuf");
+	rpc->wbuf = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rpc->wbuf))
+		rpc->wbuf = NULL;
+
+	rpc->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+	if (IS_ERR(rpc->rstc))
+		return PTR_ERR(rpc->rstc);
+
+	pm_runtime_enable(&pdev->dev);
+	ctlr->auto_runtime_pm = true;
+
+	ctlr->num_chipselect = 1;
+	ctlr->mem_ops = &rpc_spi_mem_ops;
+	ctlr->transfer_one_message = rpc_spi_transfer_one_message;
+
+	ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
+	ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_QUAD | SPI_RX_QUAD;
+
+	rpc_spi_hw_init(rpc);
+
+	ret = spi_register_controller(ctlr);
+	if (ret) {
+		dev_err(&pdev->dev, "spi_register_controller failed\n");
+		goto err_put_ctlr;
+	}
+	return 0;
+
+err_put_ctlr:
+	spi_controller_put(ctlr);
+	pm_runtime_disable(&pdev->dev);
+
+	return ret;
+}
+
+static int rpc_spi_remove(struct platform_device *pdev)
+{
+	struct spi_controller *ctlr = platform_get_drvdata(pdev);
+
+	pm_runtime_disable(&pdev->dev);
+	spi_unregister_controller(ctlr);
+
+	return 0;
+}
+
+static const struct of_device_id rpc_spi_of_ids[] = {
+	{ .compatible = "renesas,r8a77995-rpc", },
+	{ .compatible = "renesas,rcar-gen3-rpc", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rpc_spi_of_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int rpc_spi_suspend(struct device *dev)
+{
+	struct spi_controller *ctlr = dev_get_drvdata(dev);
+
+	return spi_controller_suspend(ctlr);
+}
+
+static int rpc_spi_resume(struct device *dev)
+{
+	struct spi_controller *ctlr = dev_get_drvdata(dev);
+
+	return spi_controller_resume(ctlr);
+}
+
+static SIMPLE_DEV_PM_OPS(rpc_spi_pm_ops, rpc_spi_suspend, rpc_spi_resume);
+#define DEV_PM_OPS	(&rpc_spi_pm_ops)
+#else
+#define DEV_PM_OPS	NULL
+#endif
+
+static struct platform_driver rpc_spi_driver = {
+	.probe = rpc_spi_probe,
+	.remove = rpc_spi_remove,
+	.driver = {
+		.name = "rpc-spi",
+		.of_match_table = rpc_spi_of_ids,
+		.pm = DEV_PM_OPS,
+	},
+};
+module_platform_driver(rpc_spi_driver);
+
+MODULE_AUTHOR("Mason Yang <masonccyang@mxic.com.tw>");
+MODULE_DESCRIPTION("Renesas R-Car Gen3 RPC-IF SPI controller driver");
+MODULE_LICENSE("GPL v2");