From 931484f9bd4eb1741588dc1574080fa4534ac5dc Mon Sep 17 00:00:00 2001
From: Dmitry Shifrin <dmitry.shifrin@cogentembedded.com>
Date: Mon, 19 Mar 2018 11:08:16 +0300
Subject: [PATCH 04/12] Renesas: RPC: Add RPC driver

This is initial commit. Driver supports single and dual mode.
Dual mode means that there are two spi-nor flashes connected.
Supports qspi flashes.

Signed-off-by: Dmitry Shifrin <dmitry.shifrin@cogentembedded.com>
---
 drivers/mtd/spi-nor/Kconfig       |    8 +
 drivers/mtd/spi-nor/Makefile      |    1 +
 drivers/mtd/spi-nor/renesas-rpc.c | 1429 +++++++++++++++++++++++++++++++++++++
 3 files changed, 1438 insertions(+)
 create mode 100644 drivers/mtd/spi-nor/renesas-rpc.c

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 4a682ee..298dc1d 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -7,6 +7,14 @@ menuconfig MTD_SPI_NOR
 
 if MTD_SPI_NOR
 
+
+config SPI_RENESAS_RPC
+	tristate "Renesas RPC controller"
+	depends on ARCH_R8A7795 || ARCH_R8A7796 || ARCH_R8A7797 || ARCH_R8A7798 || COMPILE_TEST
+	help
+	  QSPI driver for Renesas SPI Multi I/O Bus controller. This controller
+	  supports normal, dual and quad spi for one or two SPI NOR Flashes.
+
 config MTD_MT81xx_NOR
 	tristate "Mediatek MT81xx SPI NOR flash controller"
 	depends on HAS_IOMEM
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index 121695e..e99d775 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_SPI_FSL_QUADSPI)	+= fsl-quadspi.o
 obj-$(CONFIG_SPI_HISI_SFC)	+= hisi-sfc.o
 obj-$(CONFIG_MTD_MT81xx_NOR)    += mtk-quadspi.o
 obj-$(CONFIG_SPI_NXP_SPIFI)	+= nxp-spifi.o
+obj-$(CONFIG_SPI_RENESAS_RPC)		+= renesas-rpc.o
diff --git a/drivers/mtd/spi-nor/renesas-rpc.c b/drivers/mtd/spi-nor/renesas-rpc.c
new file mode 100644
index 0000000..1e93c98
--- /dev/null
+++ b/drivers/mtd/spi-nor/renesas-rpc.c
@@ -0,0 +1,1429 @@
+/*
+ * Renesas RPC driver
+ *
+ * Copyright (C) 2018, Cogent Embedded Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/mtd/spi-nor.h>
+
+
+
+#define CMNCR		0x0000
+#define SSLDR		0x0004
+#define DRCR		0x000C
+#define DRCMR		0x0010
+#define DREAR		0x0014
+#define DROPR		0x0018
+#define DRENR		0x001C
+#define SMCR		0x0020
+#define SMCMR		0x0024
+#define SMADR		0x0028
+#define SMOPR		0x002C
+#define SMENR		0x0030
+#define SMRDR0		0x0038
+#define SMRDR1		0x003C
+#define SMWDR0		0x0040
+#define SMWDR1		0x0044
+#define CMNSR		0x0048
+#define DRDMCR		0x0058
+#define DRDRENR		0x005C
+#define SMDMCR		0x0060
+#define SMDRENR		0x0064
+#define PHYCNT		0x007C
+#define PHYOFFSET1	0x0080
+#define PHYOFFSET2	0x0084
+#define PHYINT		0x0088
+#define DIV_REG		0x00A8
+
+/* CMNCR */
+#define CMNCR_BSZ_MASK		(0x03)
+#define CMNCR_BSZ_4x1		(0x0)
+#define CMNCR_BSZ_8x1		(0x1)
+#define CMNCR_BSZ_4x2		(0x1)
+#define CMNCR_MD		(0x1 << 31)
+#define CMNCR_MOIIO3_MASK	(0x3 << 22)
+#define CMNCR_MOIIO3_HIZ	(0x3 << 22)
+#define CMNCR_MOIIO2_MASK	(0x3 << 20)
+#define CMNCR_MOIIO2_HIZ	(0x3 << 20)
+#define CMNCR_MOIIO1_MASK	(0x3 << 18)
+#define CMNCR_MOIIO1_HIZ	(0x3 << 18)
+#define CMNCR_MOIIO0_MASK	(0x3 << 16)
+#define CMNCR_MOIIO0_HIZ	(0x3 << 16)
+#define CMNCR_IO0FV_MASK	(0x3 << 8)
+#define CMNCR_IO0FV_HIZ		(0x3 << 8)
+
+
+/* DRCR */
+#define DRCR_RBURST_MASK	(0x1f << 16)
+#define DRCR_RBURST(v)		(((v) & 0x1f) << 16)
+#define DRCR_SSLE		(0x1)
+#define DRCR_RBE		(0x1 << 8)
+#define DRCR_RCF		(0x1 << 9)
+#define DRCR_RBURST_32		(0x1f << 16)
+
+
+/* SMENR */
+#define SMENR_CDB_MASK		(0x03 << 30)
+#define SMENR_CDB(v)		(((v) & 0x03) << 30)
+#define SMENR_CDB_1B		(0)
+#define SMENR_CDB_2B		(0x1 << 30)
+#define SMENR_CDB_4B		(0x2 << 30)
+#define SMENR_OCDB_MASK		(0x03 << 28)
+#define SMENR_OCDB_1B		(0)
+#define SMENR_OCDB_2B		(0x1 << 28)
+#define SMENR_OCDB_4B		(0x2 << 28)
+#define SMENR_ADB_MASK		(0x03 << 24)
+#define SMENR_ADB(v)		(((v) & 0x03) << 24)
+#define SMENR_ADB_1B		(0)
+#define SMENR_ADB_2B		(0x1 << 24)
+#define SMENR_ADB_4B		(0x2 << 24)
+#define SMENR_OPDB_MASK		(0x03 << 20)
+#define SMENR_OPDB_1B		(0)
+#define SMENR_OPDB_2B		(0x1 << 20)
+#define SMENR_OPDB_4B		(0x2 << 20)
+#define SMENR_SPIDB_MASK	(0x03 << 16)
+#define SMENR_SPIDB(v)		(((v) & 0x03) << 16)
+#define SMENR_SPIDB_1B		(0)
+#define SMENR_SPIDB_2B		(0x1 << 16)
+#define SMENR_SPIDB_4B		(0x2 << 16)
+#define SMENR_OPDE_MASK		(0xf << 4)
+#define SMENR_OPDE_DISABLE	(0)
+#define SMENR_OPDE3		(0x8 << 4)
+#define SMENR_OPDE32		(0xC << 4)
+#define SMENR_OPDE321		(0xE << 4)
+#define SMENR_OPDE3210		(0xF << 4)
+#define SMENR_SPIDE_MASK	(0x0F)
+#define SMENR_SPIDE_DISABLE	(0)
+#define SMENR_SPIDE_8B		(0x08)
+#define SMENR_SPIDE_16B		(0x0C)
+#define SMENR_SPIDE_32B		(0x0F)
+#define SMENR_DME		(1<<15)
+#define SMENR_CDE		(1<<14)
+#define SMENR_OCDE		(1<<12)
+#define SMENR_ADE_MASK		(0xf << 8)
+#define SMENR_ADE_DISABLE	(0)
+#define SMENR_ADE_23_16		(0x4 << 8)
+#define SMENR_ADE_23_8		(0x6 << 8)
+#define SMENR_ADE_23_0		(0x7 << 8)
+#define SMENR_ADE_31_0		(0xf << 8)
+
+
+/* SMCMR */
+#define SMCMR_CMD(cmd)		(((cmd) & 0xff) << 16)
+#define SMCMR_CMD_MASK		(0xff << 16)
+#define SMCMR_OCMD(cmd)		(((cmd) & 0xff))
+#define SMCMR_OCMD_MASK		(0xff)
+
+
+/* SMDRENR */
+#define SMDRENR_HYPE_MASK	(0x7 << 12)
+#define SMDRENR_HYPE_SPI_FLASH	(0x0)
+#define SMDRENR_ADDRE		(0x1 << 8)
+#define SMDRENR_OPDRE		(0x1 << 4)
+#define SMDRENR_SPIDRE		(0x1)
+
+/* PHYCNT */
+#define PHYCNT_CAL		(0x1 << 31)
+#define PHYCNT_OCTA_MASK	(0x3 << 22)
+#define PHYCNT_EXDS		(0x1 << 21)
+#define PHYCNT_OCT		(0x1 << 20)
+#define PHYCNT_DDRCAL		(0x1 << 19)
+#define PHYCNT_HS		(0x1 << 18)
+#define PHYCNT_STREAM_MASK	(0x7 << 15)
+#define PHYCNT_STREAM(o)	(((o) & 0x7) << 15)
+#define PHYCNT_WBUF2		(0x1 << 4)
+#define PHYCNT_WBUF		(0x1 << 2)
+#define PHYCNT_PHYMEM_MASK	(0x3)
+
+/* SMCR */
+#define SMCR_SSLKP		(0x1 << 8)
+#define SMCR_SPIRE		(0x1 << 2)
+#define SMCR_SPIWE		(0x1 << 1)
+#define SMCR_SPIE		(0x1)
+
+/* CMNSR */
+#define	CMNSR_TEND		(0x1 << 0)
+
+/* SSLDR */
+#define SSLDR_SPNDL(v)		(((v) & 0x7) << 16)
+#define SSLDR_SLNDL(v)		((((v) | 0x4) & 0x7) << 8)
+#define SSLDR_SCKDL(v)		((v) & 0x7)
+
+/* DREAR */
+#define DREAR_EAV_MASK		(0xff << 16)
+#define DREAR_EAV(v)		(((v) & 0xff) << 16)
+#define DREAR_EAC_MASK		(0x7)
+#define DREAR_24B		(0)
+#define DREAR_25B		(1)
+
+/* DRENR */
+#define DRENR_CDB_MASK		(0x03 << 30)
+#define DRENR_CDB(v)		(((v) & 0x3) << 30)
+#define DRENR_CDB_1B		(0)
+#define DRENR_CDB_2B		(0x1 << 30)
+#define DRENR_CDB_4B		(0x2 << 30)
+#define DRENR_OCDB_MASK		(0x03 << 28)
+#define DRENR_OCDB_1B		(0)
+#define DRENR_OCDB_2B		(0x1 << 28)
+#define DRENR_OCDB_4B		(0x2 << 28)
+#define DRENR_ADB_MASK		(0x03 << 24)
+#define DRENR_ADB(v)		(((v) & 0x3) << 24)
+#define DRENR_ADB_1B		(0)
+#define DRENR_ADB_2B		(0x1 << 24)
+#define DRENR_ADB_4B		(0x2 << 24)
+#define DRENR_OPDB_MASK		(0x03 << 20)
+#define DRENR_OPDB_1B		(0)
+#define DRENR_OPDB_2B		(0x1 << 20)
+#define DRENR_OPDB_4B		(0x2 << 20)
+#define DRENR_DRDB_MASK		(0x03 << 16)
+#define DRENR_DRDB(v)		(((v) & 0x3) << 16)
+#define DRENR_DRDB_1B		(0)
+#define DRENR_DRDB_2B		(0x1 << 16)
+#define DRENR_DRDB_4B		(0x2 << 16)
+#define DRENR_OPDE_MASK		(0xf << 4)
+#define DRENR_OPDE_DISABLE	(0)
+#define DRENR_OPDE3		(0x8 << 4)
+#define DRENR_OPDE32		(0xC << 4)
+#define DRENR_OPDE321		(0xE << 4)
+#define DRENR_OPDE3210		(0xF << 4)
+#define DRENR_DME		(1<<15)
+#define DRENR_CDE		(1<<14)
+#define DRENR_OCDE		(1<<12)
+#define DRENR_ADE_MASK		(0xf << 8)
+#define DRENR_ADE_DISABLE	(0)
+#define DRENR_ADE_23_0		(0x7 << 8)
+#define DRENR_ADE_31_0		(0xf << 8)
+
+/* DRCMR */
+#define DRCMR_CMD(cmd)		(((cmd) & 0xff) << 16)
+#define DRCMR_CMD_MASK		(0xff << 16)
+#define DRCMR_OCMD(cmd)		(((cmd) & 0xff))
+#define DRCMR_OCMD_MASK		(0xff)
+
+/* DRCMR */
+#define DRDMCR_DMCYC(v)		((v) & 0x1f)
+#define DRDMCR_DMCYC_MASK	(0x1f)
+
+/* SMDMCR */
+#define SMDMCR_DMCYC(v)		((v) & 0x0f)
+#define SMDMCR_DMCYC_MASK	(0x0f)
+
+/* PHYOFFSET1 */
+#define PHYOFFSET1_DDRTMG	(1 << 28)
+
+/* DIVREG */
+#define DIVREG_RATIO_MASK	(0x03)
+#define DIVREG_RATIO(v)		((v) & 0x03)
+#define DIVREG_RATIO_MAX	(0x2)
+
+
+#define DEFAULT_TO		(100)
+#define WRITE_BUF_SIZE		(0x100)
+#define WRITE_BUF_ADR_MASK	(0xff)
+
+#define REPEAT_MAX		(20)
+#define REPEAT_TIME		(10)
+
+
+struct rpc_spi {
+	struct platform_device *pdev;
+	void __iomem *base;
+	void __iomem *read_area;
+	void __iomem *write_area;
+	struct clk *clk;
+	unsigned int irq;
+	struct spi_nor	spi_nor;
+
+#define MTD_QSPI_1x	0
+#define MTD_QSPI_2x	1
+
+	u32 mtdtype;
+};
+
+
+
+/* IP block use it's own clock divigion register */
+#define OWN_CLOCK_DIVIDER	BIT(0)
+
+
+
+static void regs_dump(struct rpc_spi *rpc)
+{
+	static u32 regs[] = {
+		CMNCR, SSLDR, DRCR, DRCMR, DREAR,
+		DROPR, DRENR, SMCR, SMCMR, SMADR,
+		SMOPR, SMENR, SMRDR0, SMRDR1, SMWDR0,
+		SMWDR1, CMNSR, DRDMCR, DRDRENR, SMDMCR,
+		SMDRENR, PHYCNT, PHYOFFSET1, PHYOFFSET2,
+		PHYINT
+	};
+
+	static const char *const names[] = {
+		"CMNCR", "SSLDR", "DRCR", "DRCMR", "DREAR",
+		"DROPR", "DRENR", "SMCR", "SMCMR", "SMADR",
+		"SMOPR", "SMENR", "SMRDR0", "SMRDR1", "SMWDR0",
+		"SMWDR1", "CMNSR", "DRDMCR", "DRDRENR", "SMDMCR",
+		"SMDRENR", "PHYCNT", "PHYOFFSET1", "PHYOFFSET2",
+		"PHYINT"
+	};
+
+	int i;
+
+	dev_dbg(&rpc->pdev->dev, "RPC regs dump:\n");
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		dev_dbg(&rpc->pdev->dev, "%s = 0x%08x\n", names[i],
+			readl(rpc->base + regs[i]));
+}
+
+
+
+
+
+static u32 rpc_read(struct rpc_spi *rpc, unsigned int reg)
+{
+	u32 val;
+
+	val = readl(rpc->base + reg);
+	return val;
+}
+
+static void rpc_write(struct rpc_spi *rpc, unsigned int reg, u32 val)
+{
+	writel(val, rpc->base + reg);
+}
+
+
+static int rpc_wait(struct rpc_spi *rpc, u32 to)
+{
+	u32 val;
+	int i;
+
+	for (i = 0; i < to; i++) {
+		val = rpc_read(rpc, CMNSR);
+		val &= CMNSR_TEND;
+		if (val)
+			break;
+
+		udelay(100);
+	}
+
+	if (i == to) {
+		dev_err(&rpc->pdev->dev, "timeout waiting for operation end %d\n",
+			rpc_read(rpc, CMNSR));
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+static int rpc_setup_clk_ratio(struct rpc_spi *rpc, u32 max_clk_rate)
+{
+	unsigned long rate = clk_get_rate(rpc->clk);
+	u32 ratio;
+	u32 val;
+
+	ratio = DIV_ROUND_UP(rate, max_clk_rate * 2) >> 1;
+	if (ratio > DIVREG_RATIO_MAX)
+		ratio = DIVREG_RATIO_MAX;
+
+	val = rpc_read(rpc, DIV_REG);
+	val &= DIVREG_RATIO_MASK;
+	val |= DIVREG_RATIO(ratio);
+	rpc_write(rpc, DIV_REG, val);
+
+	return 0;
+}
+
+static int rpc_endisable_write_buf(struct rpc_spi *rpc, bool en)
+{
+	u32 val;
+
+	val = rpc_read(rpc, PHYCNT);
+
+	if (en)
+		val |= PHYCNT_WBUF | PHYCNT_WBUF2;
+	else
+		val &= ~(PHYCNT_WBUF | PHYCNT_WBUF2);
+
+	rpc_write(rpc, PHYCNT, val);
+
+	return 0;
+}
+
+
+static int rpc_begin(struct rpc_spi *rpc,
+			bool rx, bool tx, bool last)
+{
+	u32 val = SMCR_SPIE;
+
+	if (rx)
+		val |= SMCR_SPIRE;
+
+	if (tx)
+		val |= SMCR_SPIWE;
+
+	if (!last)
+		val |= SMCR_SSLKP;
+
+	rpc_write(rpc, SMCR, val);
+
+	return 0;
+}
+
+
+static int rpc_setup_reg_mode(struct rpc_spi *rpc)
+{
+	u32 val;
+
+	rpc_wait(rpc, DEFAULT_TO);
+
+	rpc_endisable_write_buf(rpc, false);
+
+	/* ...setup manual mode */
+	val = rpc_read(rpc, CMNCR);
+	val |=  CMNCR_MD;
+	rpc_write(rpc, CMNCR, val);
+
+	/* disable ddr */
+	val = rpc_read(rpc, SMDRENR);
+	val &= ~(SMDRENR_ADDRE | SMDRENR_OPDRE | SMDRENR_SPIDRE);
+	rpc_write(rpc, SMDRENR, val);
+
+	/* enable 1bit command */
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_CDB_MASK | SMENR_OCDB_MASK | SMENR_DME
+		 | SMENR_OCDE | SMENR_SPIDB_MASK
+		 | SMENR_ADE_MASK | SMENR_ADB_MASK
+		 | SMENR_OPDE_MASK | SMENR_SPIDE_MASK);
+	val |= SMENR_CDB_1B | SMENR_CDE | SMENR_SPIDE_32B;
+	rpc_write(rpc, SMENR, val);
+
+
+	return 0;
+}
+
+static void rpc_flush_cache(struct rpc_spi *rpc)
+{
+	u32 val;
+
+	val = rpc_read(rpc, DRCR);
+	val |= DRCR_RCF;
+
+	rpc_write(rpc, DRCR, val);
+}
+
+
+static int rpc_setup_ext_mode(struct rpc_spi *rpc)
+{
+	u32 val;
+	u32 cmncr;
+
+	rpc_wait(rpc, DEFAULT_TO);
+
+	rpc_endisable_write_buf(rpc, false);
+
+	/* ...setup ext mode */
+	val = rpc_read(rpc, CMNCR);
+	cmncr = val;
+	val &=  ~(CMNCR_MD);
+	rpc_write(rpc, CMNCR, val);
+
+	/* ...enable burst and clear cache */
+	val = rpc_read(rpc, DRCR);
+	val &= ~(DRCR_RBURST_MASK | DRCR_RBE | DRCR_SSLE);
+	val |= DRCR_RBURST(0x1f) | DRCR_RBE;
+
+	if (cmncr & CMNCR_MD)
+		val |= DRCR_RCF;
+
+	rpc_write(rpc, DRCR, val);
+
+	return 0;
+}
+
+
+static int rpc_setup_data_size(struct rpc_spi *rpc, u32 size, bool copy)
+{
+	u32 val;
+
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_SPIDE_MASK);
+
+	if (rpc->mtdtype == MTD_QSPI_2x && !copy)
+		size >>= 1;
+
+	switch (size) {
+	case 0:
+		break;
+	case 1:
+		val |= SMENR_SPIDE_8B;
+		break;
+	case 2:
+		val |= SMENR_SPIDE_16B;
+		break;
+	case 4:
+		val |= SMENR_SPIDE_32B;
+		break;
+	default:
+		dev_err(&rpc->pdev->dev, "Unsupported data width %d\n", size);
+		return -EINVAL;
+	}
+	rpc_write(rpc, SMENR, val);
+
+	return 0;
+}
+
+
+static int rpc_setup_extmode_read_addr(struct rpc_spi *rpc,
+					int adr_width, loff_t adr)
+{
+	u32 val;
+	u32 v;
+
+	val = rpc_read(rpc, DREAR);
+	val &= ~(DREAR_EAV_MASK | DREAR_EAC_MASK);
+
+	if (adr_width == 4) {
+		v = adr >> 25;
+		val |= DREAR_EAV(v) | DREAR_25B;
+	}
+	rpc_write(rpc, DREAR, val);
+
+	val = rpc_read(rpc, DRENR);
+	val &= ~(DRENR_ADE_MASK);
+	if (adr_width == 4)
+		val |= DRENR_ADE_31_0;
+	else
+		val |= DRENR_ADE_23_0;
+	rpc_write(rpc, DRENR, val);
+
+	return 0;
+}
+
+
+
+#define NBITS_TO_VAL(v)	((v >> 1) & 3)
+static int rpc_setup_extmode_nbits(struct rpc_spi *rpc, int cnb,
+					int anb, int dnb)
+{
+	u32 val;
+
+	val = rpc_read(rpc, DRENR);
+	val &= ~(DRENR_CDB_MASK | DRENR_ADB_MASK | DRENR_DRDB_MASK);
+	val |= DRENR_CDB(NBITS_TO_VAL(cnb))
+		| DRENR_ADB(NBITS_TO_VAL(anb))
+		| DRENR_DRDB(NBITS_TO_VAL(dnb));
+	rpc_write(rpc, DRENR, val);
+
+	return 0;
+}
+
+static int rpc_setup_writemode_nbits(struct rpc_spi *rpc, int cnb,
+					int anb, int dnb)
+{
+	u32 val;
+
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_CDB_MASK | SMENR_ADB_MASK | SMENR_SPIDB_MASK);
+	val |= SMENR_CDB(NBITS_TO_VAL(cnb))
+		| SMENR_ADB(NBITS_TO_VAL(anb))
+		| SMENR_SPIDB(NBITS_TO_VAL(dnb));
+	rpc_write(rpc, SMENR, val);
+
+	return 0;
+}
+
+static void rpc_setup_write_mode_command_and_adr(struct rpc_spi *rpc,
+						int adr_width, bool ena)
+{
+	u32 val;
+
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_CDB_MASK | SMENR_CDE | SMENR_ADE_MASK);
+
+	if (ena) {
+		/* enable 1bit command */
+		val |= SMENR_CDB_1B | SMENR_CDE;
+
+		if (adr_width == 4)
+			val |= SMENR_ADE_31_0;
+		else
+			val |= SMENR_ADE_23_0;
+	}
+	rpc_write(rpc, SMENR, val);
+}
+
+static int rpc_setup_write_mode(struct rpc_spi *rpc)
+{
+	u32 val;
+
+	rpc_wait(rpc, DEFAULT_TO);
+
+	rpc_endisable_write_buf(rpc, true);
+
+	/* ...setup manual mode */
+	val = rpc_read(rpc, CMNCR);
+	val |=  CMNCR_MD;
+	rpc_write(rpc, CMNCR, val);
+
+	/* disable ddr */
+	val = rpc_read(rpc, SMDRENR);
+	val &= ~(SMDRENR_ADDRE | SMDRENR_OPDRE | SMDRENR_SPIDRE);
+	rpc_write(rpc, SMDRENR, val);
+
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_OCDB_MASK | SMENR_DME | SMENR_OCDE | SMENR_SPIDB_MASK
+		 | SMENR_ADB_MASK | SMENR_OPDE_MASK | SMENR_SPIDE_MASK);
+	val |= SMENR_SPIDE_32B;
+	rpc_write(rpc, SMENR, val);
+
+	return 0;
+}
+
+static void rpc_read_manual_data(struct rpc_spi *rpc, u32 *pv0, u32 *pv1)
+{
+	u32 val0, val1, rd0, rd1;
+
+	val0 = rpc_read(rpc, SMRDR0);
+	val1 = rpc_read(rpc, SMRDR1);
+
+	if (rpc->mtdtype == MTD_QSPI_2x) {
+		rd1 = (val0 & 0xff000000) | ((val0 << 8) & 0xff0000) |
+			((val1 >> 16) & 0xff00) | ((val1 >> 8) & 0xff);
+		rd0 = ((val0 & 0xff0000) << 8) | ((val0 << 16) & 0xff0000) |
+			((val1 >> 8) & 0xff00) | (val1 & 0xff);
+	} else
+		rd0 = val0;
+
+	if (pv0)
+		*pv0 = rd0;
+
+	if (pv1 && rpc->mtdtype == MTD_QSPI_2x)
+		*pv1 = rd1;
+}
+
+
+static int rpc_datalen2trancfersize(struct rpc_spi *rpc, int len, bool copy)
+{
+	int sz = len;
+
+	if (len >= 2)
+		sz = 2;
+
+	if (len >= 4)
+		sz = 4;
+
+	if (rpc->mtdtype == MTD_QSPI_2x
+		&& len >= 8 && !copy)
+		sz = 8;
+
+	return sz;
+}
+
+static int __rpc_write_data2reg(struct rpc_spi *rpc, int off,
+					const u8 *buf, int sz)
+{
+	const u32 *b32 = (const u32 *)buf;
+	const u16 *b16 = (const u16 *)buf;
+
+	if (sz == 4)
+		rpc_write(rpc, off, *b32);
+	else if (sz == 2)
+		writew(*b16, rpc->base+off);
+	else if (sz == 1)
+		writeb(*buf, rpc->base+off);
+	else if (sz != 0) {
+		dev_err(&rpc->pdev->dev, "incorrect data size %d\n", sz);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+#define __SETVAL(x)	((((x) & 0xff) << 8) | ((x) & 0xff))
+static int rpc_write_data2reg(struct rpc_spi *rpc, const u8 *buf,
+				int sz, bool copy)
+{
+	int i, ret;
+	u32 v = 0;
+
+	if (rpc->mtdtype == MTD_QSPI_2x) {
+		if (copy) {
+			for (i = 0; i < sz && i < 2; i++)
+				v |= (__SETVAL(buf[i]) << 16*i);
+
+			ret = __rpc_write_data2reg(rpc,
+						   sz == 4 ? SMWDR1 : SMWDR0,
+						   (u8 *)&v,
+						   sz == 4 ? sz : sz * 2);
+			if (ret)
+				return ret;
+
+			v = 0;
+			for (; i < sz; i++)
+				v |= (__SETVAL(buf[i]) << 16*i);
+
+
+			ret = __rpc_write_data2reg(rpc,
+						   sz == 4 ? SMWDR0 : SMWDR1,
+						   (u8 *)&v,
+						   sz == 4 ? sz : sz * 2);
+			if (ret)
+				return ret;
+
+			return 0;
+		}
+
+		sz >>= 1;
+		ret = __rpc_write_data2reg(rpc,
+					   sz == 4 ? SMWDR1 : SMWDR0,
+					   buf,
+					   sz == 4 ? sz : sz * 2);
+		if (ret)
+			return ret;
+		buf += sz;
+
+		return __rpc_write_data2reg(rpc,
+					    sz == 4 ? SMWDR0 : SMWDR1,
+					    buf, sz == 4 ? sz : sz * 2);
+	}
+
+	return __rpc_write_data2reg(rpc, SMWDR0, buf, sz);
+}
+
+static ssize_t rpc_write_unaligned(struct spi_nor *nor, loff_t to, size_t len,
+			       const u_char *buf, size_t fullen)
+{
+	int ret = len, dsize;
+	struct rpc_spi *rpc = nor->priv;
+	bool copy = false, last;
+	loff_t _to;
+
+	rpc_endisable_write_buf(rpc, false);
+
+	while (len > 0) {
+		_to = to;
+		if (rpc->mtdtype == MTD_QSPI_2x)
+			_to >>= 1;
+		rpc_write(rpc, SMADR, _to);
+		dsize = rpc_datalen2trancfersize(rpc, len, copy);
+
+		if (rpc_setup_data_size(rpc, dsize, copy))
+			return -EINVAL;
+
+		rpc_write_data2reg(rpc, buf, dsize, copy);
+
+		last = (len <= dsize && fullen <= ret);
+		rpc_begin(rpc, false, true, last);
+		if (rpc_wait(rpc, DEFAULT_TO))
+			return -ETIMEDOUT;
+
+		/* ...disable command */
+		rpc_setup_write_mode_command_and_adr(rpc,
+					nor->addr_width, false);
+
+		buf += dsize;
+		len -= dsize;
+		to += dsize;
+	}
+
+	return ret;
+}
+
+
+
+
+static ssize_t rpc_write_flash(struct spi_nor *nor, loff_t to, size_t len,
+			       const u_char *buf)
+{
+	ssize_t res = len, full = len;
+	u32 val;
+	u8 rval[2];
+	struct rpc_spi *rpc = nor->priv;
+	loff_t bo;
+	loff_t offset;
+	loff_t _to;
+	bool is_rounded = false;
+
+	/* ...len should be rounded to 2 bytes */
+	if (rpc->mtdtype == MTD_QSPI_2x && (len & 1)) {
+		is_rounded = true;
+		len &= ~(1);
+	}
+
+	bo = to & (WRITE_BUF_ADR_MASK);
+
+	rpc_flush_cache(rpc);
+	rpc_setup_write_mode(rpc);
+	rpc_setup_write_mode_command_and_adr(rpc, nor->addr_width, true);
+	rpc_setup_writemode_nbits(rpc, 1, 1, 1);
+
+	/* ...setup command */
+	val = rpc_read(rpc, SMCMR);
+	val &= ~(SMCMR_CMD_MASK);
+	val |= SMCMR_CMD(nor->program_opcode);
+	rpc_write(rpc, SMCMR, val);
+
+	offset = (to & (~WRITE_BUF_ADR_MASK));
+
+	/* ...write unaligned first bytes */
+	if (bo) {
+		rpc_write_unaligned(nor, to, WRITE_BUF_SIZE - bo, buf, full);
+		rpc_setup_write_mode(rpc);
+
+		len -= WRITE_BUF_SIZE - bo;
+		buf += WRITE_BUF_SIZE - bo;
+		to += WRITE_BUF_SIZE - bo;
+		full -= WRITE_BUF_SIZE - bo;
+	}
+
+	/* Unfortunatly RPC does not write properly in write buf mode
+	 * without transferring command.
+	 * May be it is better just remove this code
+	 */
+	if (len >= WRITE_BUF_SIZE && !bo) {
+		_to = to;
+		if (rpc->mtdtype == MTD_QSPI_2x)
+			_to >>= 1;
+		rpc_write(rpc, SMADR, _to);
+		memcpy_toio(rpc->write_area, buf, WRITE_BUF_SIZE);
+		buf += WRITE_BUF_SIZE;
+		len -= WRITE_BUF_SIZE;
+		to += WRITE_BUF_SIZE;
+		full -= WRITE_BUF_SIZE;
+
+		rpc_begin(rpc, false, true, full <= 0);
+		if (rpc_wait(rpc, DEFAULT_TO))
+			return -ETIMEDOUT;
+
+		rpc_setup_write_mode_command_and_adr(rpc,
+					nor->addr_width, false);
+	}
+
+	if (len) {
+		rpc_write_unaligned(nor, to, len, buf, full);
+		buf += len;
+		to += len;
+		full -= len;
+		len = 0;
+	}
+
+	if (is_rounded) {
+		rval[0] = *buf;
+		rval[1] = 0xFF;
+		rpc_write_unaligned(nor, to, 2, rval, full);
+	}
+
+	rpc_flush_cache(rpc);
+
+	return res;
+}
+
+static inline unsigned int rpc_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;
+	}
+}
+
+
+#if 0
+//manual read
+static ssize_t rpc_read_flash(struct spi_nor *nor, loff_t from, size_t len,
+			   u_char *buf)
+{
+	ssize_t ret = len;
+	struct rpc_spi *rpc = nor->priv;
+	int adr_width = nor->addr_width;
+	int opcode_nbits = 1;//SPI_NBITS_SINGLE;
+	int addr_nbits = spi_nor_get_read_addr_nbits(nor->read_opcode);
+	int data_nbits = rpc_rx_nbits(nor);
+	int dummy = nor->read_dummy - 1;
+	u32 val, val2;
+	u32 *buf32;
+	int sz = 4;
+	int i, j;
+	loff_t adr = from;
+
+	rpc_wait(rpc, DEFAULT_TO);
+	if (rpc->mtdtype == MTD_QSPI_2x)
+		sz <<= 1;
+
+	/* ...setup manual mode */
+	val = rpc_read(rpc, CMNCR);
+	val |=  CMNCR_MD;
+	rpc_write(rpc, CMNCR, val);
+
+	/*...setup dummy */
+	val = rpc_read(rpc, SMDMCR);
+	val &= ~(SMDMCR_DMCYC_MASK);
+	val |= SMDMCR_DMCYC(dummy);
+	rpc_write(rpc, SMDMCR, val);
+
+	/* disable ddr */
+	val = rpc_read(rpc, SMDRENR);
+	val &= ~(SMDRENR_ADDRE | SMDRENR_OPDRE | SMDRENR_SPIDRE);
+	rpc_write(rpc, SMDRENR, val);
+
+	/* enable 1bit command */
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_CDB_MASK | SMENR_OCDB_MASK | SMENR_DME
+		 | SMENR_OCDE | SMENR_SPIDB_MASK
+		 | SMENR_ADE_MASK | SMENR_ADB_MASK
+		 | SMENR_OPDE_MASK | SMENR_SPIDE_MASK);
+	val |= SMENR_CDB_1B | SMENR_ADB_4B | SMENR_SPIDB_4B
+		| SMENR_CDE | SMENR_ADE_31_0 | SMENR_DME | SMENR_SPIDE_32B;
+	rpc_write(rpc, SMENR, val);
+
+	/* ...setup command */
+	val = rpc_read(rpc, SMCMR);
+	val &= ~(SMCMR_CMD_MASK);
+	val |= SMCMR_CMD(nor->read_opcode);
+	rpc_write(rpc, SMCMR, val);
+
+	buf32 = (u32 *)buf;
+
+	while (len > 0) {
+		adr = from + ret - len;
+
+		if (rpc->mtdtype == MTD_QSPI_2x)
+			adr >>= 1;
+
+		rpc_write(rpc, SMADR, adr);
+
+		rpc_begin(rpc, true, false, len <= sz);
+		if (rpc_wait(rpc, DEFAULT_TO))
+			return -ETIMEDOUT;
+
+		rpc_read_manual_data(rpc, &val, &val2);
+
+		j = 0;
+		do {
+			if (len > 4) {
+				*buf32 = val;
+				buf32++;
+				len -= 4;
+			} else {
+				buf = (u8 *)buf32;
+				for (i = 0; i < len; i++) {
+					*buf = (val >> (8 * i)) & 0x000000ff;
+					buf++;
+				}
+				len = 0;
+			}
+			val = val2;
+			j++;
+		} while (j < 2 && rpc->mtdtype == MTD_QSPI_2x);
+	}
+
+	return ret;
+}
+#else
+#define READ_ADR_MASK		(BIT(26) - 1)
+static ssize_t rpc_read_flash(struct spi_nor *nor, loff_t from, size_t len,
+			   u_char *buf)
+{
+	u32 val;
+	struct rpc_spi *rpc = nor->priv;
+	int adr_width = nor->addr_width;
+	int opcode_nbits = 1;
+	int addr_nbits = spi_nor_get_read_addr_nbits(nor->read_opcode);
+	int data_nbits = rpc_rx_nbits(nor);
+	int dummy = nor->read_dummy - 1;
+	ssize_t ret = len;
+	ssize_t readlen;
+	loff_t _from;
+
+	rpc_setup_ext_mode(rpc);
+	/* ...setup n bits */
+	rpc_setup_extmode_nbits(rpc, opcode_nbits, addr_nbits, data_nbits);
+
+	/* TODO: setup DDR */
+
+	/* ...setup command */
+	val = rpc_read(rpc, DRCMR);
+	val &= ~(DRCMR_CMD_MASK);
+	val |= DRCMR_CMD(nor->read_opcode);
+	rpc_write(rpc, DRCMR, val);
+
+	/* ...setup dummy cycles */
+	val = rpc_read(rpc, DRDMCR);
+	val &= ~(DRDMCR_DMCYC_MASK);
+	val |= DRDMCR_DMCYC(dummy);
+	rpc_write(rpc, DRDMCR, val);
+
+	/* ...setup read sequence */
+	val = rpc_read(rpc, DRENR);
+	val |= DRENR_DME | DRENR_CDE;
+	rpc_write(rpc, DRENR, val);
+
+	while (len > 0) {
+		/* ...setup address */
+		rpc_setup_extmode_read_addr(rpc, adr_width, from);
+		/* ...use adr [25...0] */
+		_from = from & READ_ADR_MASK;
+
+		readlen = READ_ADR_MASK - _from + 1;
+		readlen = readlen > len ? len : readlen;
+
+		memcpy_fromio(buf, rpc->read_area + _from, readlen);
+		buf += readlen;
+		from += readlen;
+		len -= readlen;
+	}
+
+	return ret;
+}
+#endif
+
+static int __rpc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+	u32 val, val2;
+	u32 *buf32;
+	int i;
+	u32 mask = 0, type;
+	struct rpc_spi *rpc = nor->priv;
+
+	type = rpc->mtdtype;
+
+	rpc_setup_reg_mode(rpc);
+	val = rpc_read(rpc, SMCMR);
+	val &= ~(SMCMR_CMD_MASK);
+	val |= SMCMR_CMD(opcode);
+	rpc_write(rpc, SMCMR, val);
+
+	rpc_begin(rpc, true, false, len <= 4);
+	if (rpc_wait(rpc, DEFAULT_TO))
+		return -ETIMEDOUT;
+
+	/* ...disable command */
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_CDE);
+	rpc_write(rpc, SMENR, val);
+
+	buf32 = (u32 *)buf;
+
+	while (len > 0) {
+		rpc_read_manual_data(rpc, &val, &val2);
+
+		if (mask) {
+			dev_warn(&rpc->pdev->dev,
+				"Using mask workaround (0x%x)\n", mask);
+			val &= ~(mask);
+			val2 &= ~(mask);
+		}
+
+		/* ... spi flashes should be the same */
+		if (type == MTD_QSPI_2x && val != val2) {
+			/* clear cs */
+			rpc_begin(rpc, true, false, true);
+			return -EAGAIN;
+		}
+
+		if (len > 4) {
+			*buf32 = val;
+			buf32++;
+			len -= 4;
+		} else {
+			buf = (u8 *)buf32;
+			for (i = 0; i < len; i++) {
+				*buf = (val >> (8 * i)) & 0x000000ff;
+				buf++;
+			}
+			len = 0;
+		}
+
+		if (!len)
+			break;
+
+		mask = 0xff;
+
+		rpc_begin(rpc, true, false, len <= 4);
+		if (rpc_wait(rpc, DEFAULT_TO))
+			return -ETIMEDOUT;
+
+	}
+
+	return 0;
+}
+
+
+static int rpc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+	int i, ret;
+
+	/* A few read commands like read status can
+	 * generate different answers. We repeat reading
+	 * in that case
+	 */
+	for (i = 0; i < REPEAT_MAX; i++) {
+		ret = __rpc_read_reg(nor, opcode, buf, len);
+		if (!ret || ret != -EAGAIN)
+			break;
+		mdelay(REPEAT_TIME);
+	}
+
+	return ret;
+}
+
+
+
+static int rpc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+	struct rpc_spi *rpc = nor->priv;
+	u32 val;
+	int dsize;
+	bool copy = true;
+
+	rpc_setup_reg_mode(rpc);
+
+	val = rpc_read(rpc, SMCMR);
+	val &= ~(SMCMR_CMD_MASK);
+	val |= SMCMR_CMD(opcode);
+	rpc_write(rpc, SMCMR, val);
+
+	dsize = rpc_datalen2trancfersize(rpc, len, copy);
+
+	if (rpc_setup_data_size(rpc, dsize, copy))
+		return -EINVAL;
+
+	if (rpc_write_data2reg(rpc, buf, dsize, copy))
+		return -EINVAL;
+	buf += dsize;
+	len -= dsize;
+	rpc_begin(rpc, false, dsize > 0, len == 0);
+
+	if (rpc_wait(rpc, DEFAULT_TO))
+		return -ETIMEDOUT;
+
+	/* ...disable command */
+	val = rpc_read(rpc, SMENR);
+	val &= ~(SMENR_CDE);
+	rpc_write(rpc, SMENR, val);
+
+	while (len > 0) {
+		dsize = rpc_datalen2trancfersize(rpc, len, copy);
+		if (rpc_setup_data_size(rpc, dsize, copy))
+			return -EINVAL;
+		rpc_write_data2reg(rpc, buf, dsize, copy);
+		buf += dsize;
+		len -= dsize;
+
+		rpc_begin(rpc, false, dsize, len == 0);
+
+		if (rpc_wait(rpc, DEFAULT_TO))
+			return -ETIMEDOUT;
+
+	}
+
+	return 0;
+}
+
+
+
+/* hw init for spi-nor flashes */
+static int rpc_hw_init_1x2x(struct rpc_spi *rpc)
+{
+	u32 val;
+
+	/* Exec calibration */
+	val = rpc_read(rpc, PHYCNT);
+	val &= ~(PHYCNT_OCTA_MASK | PHYCNT_EXDS | PHYCNT_OCT
+		 | PHYCNT_DDRCAL | PHYCNT_HS | PHYCNT_STREAM_MASK
+		 | PHYCNT_WBUF2 | PHYCNT_WBUF | PHYCNT_PHYMEM_MASK);
+	val |= (PHYCNT_CAL) | PHYCNT_STREAM(6);
+	rpc_write(rpc, PHYCNT, val);
+
+	/* disable rpc_* pins  */
+	val = rpc_read(rpc, PHYINT);
+	val &= ~((1<<24) | (7<<16));
+	rpc_write(rpc, PHYINT, val);
+
+	val = rpc_read(rpc, SMDRENR);
+	val &= ~(SMDRENR_HYPE_MASK);
+	val |= SMDRENR_HYPE_SPI_FLASH;
+	rpc_write(rpc, SMDRENR, val);
+
+	val = rpc_read(rpc, CMNCR);
+	val &=  ~(CMNCR_BSZ_MASK);
+	if (rpc->mtdtype != MTD_QSPI_1x)
+		val |= CMNCR_BSZ_4x2;
+	rpc_write(rpc, CMNCR, val);
+
+	val = rpc_read(rpc, PHYOFFSET1);
+	val |= PHYOFFSET1_DDRTMG;
+	rpc_write(rpc, PHYOFFSET1, val);
+
+	val = SSLDR_SPNDL(0) | SSLDR_SLNDL(4) | SSLDR_SCKDL(0);
+	rpc_write(rpc, SSLDR, val);
+
+	return 0;
+}
+
+
+static int rpc_hw_init(struct rpc_spi *rpc)
+{
+	switch (rpc->mtdtype) {
+	case MTD_QSPI_1x:
+	case MTD_QSPI_2x:
+		return rpc_hw_init_1x2x(rpc);
+
+	default:
+		dev_err(&rpc->pdev->dev, "Unsupported connection mode\n");
+		return -ENODEV;
+	}
+}
+
+
+static int rpc_erase_sector(struct spi_nor *nor, loff_t addr)
+{
+	struct rpc_spi *rpc = nor->priv;
+	u8 buf[6];
+	int i;
+
+	if (rpc->mtdtype == MTD_QSPI_2x)
+		addr >>= 1;
+
+	for (i = nor->addr_width - 1; i >= 0; i--) {
+		buf[i] = addr & 0xff;
+		addr >>= 8;
+	}
+
+	return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
+}
+
+
+
+
+static const struct of_device_id rpc_of_match[] = {
+	{ .compatible = "renesas,qspi-rpc-r8a7798" },
+	{ .compatible = "renesas,qspi-rpc-r8a7797", .data = (void *)OWN_CLOCK_DIVIDER },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(of, rpc_of_match);
+
+
+
+static int rpc_spi_probe(struct platform_device *pdev)
+{
+	struct device_node *flash_np;
+	struct spi_nor *nor;
+	struct rpc_spi *rpc;
+	struct resource *res;
+	enum read_mode mode = SPI_NOR_NORMAL;
+	u32 max_clk_rate = 50000000;
+	u32 property;
+	int ret;
+	int own_clk;
+
+
+	flash_np = of_get_next_available_child(pdev->dev.of_node, NULL);
+	if (!flash_np) {
+		dev_err(&pdev->dev, "no SPI flash device to configure\n");
+		return -ENODEV;
+	}
+
+	if (!of_property_read_u32(flash_np, "spi-rx-bus-width", &property)) {
+		switch (property) {
+		case 1:
+			break;
+		case 2:
+			mode = SPI_NOR_DUAL;
+			break;
+		case 4:
+			mode = SPI_NOR_QUAD;
+			break;
+		default:
+			dev_err(&pdev->dev, "unsupported rx-bus-width\n");
+			return -EINVAL;
+		}
+	}
+
+	of_property_read_u32(flash_np, "spi-max-frequency", &max_clk_rate);
+	own_clk = of_device_get_match_data(&pdev->dev);
+
+	rpc = devm_kzalloc(&pdev->dev, sizeof(*rpc), GFP_KERNEL);
+	if (!rpc)
+		return -ENOMEM;
+
+	rpc->pdev = pdev;
+
+	/* ... setup nor hooks */
+	nor = &rpc->spi_nor;
+	nor->dev = &pdev->dev;
+	spi_nor_set_flash_node(nor, flash_np);
+	nor->read  = rpc_read_flash;
+	nor->write = rpc_write_flash;
+	nor->read_reg  = rpc_read_reg;
+	nor->write_reg = rpc_write_reg;
+	nor->priv = rpc;
+	rpc->mtdtype = MTD_QSPI_1x;
+	if (of_find_property(pdev->dev.of_node, "dual", NULL)) {
+		rpc->mtdtype = MTD_QSPI_2x;
+		spi_nor_set_array_size(nor, 2);
+	}
+	if (rpc->mtdtype == MTD_QSPI_2x)
+		nor->erase = rpc_erase_sector;
+
+	/* ...get memory */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	rpc->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rpc->base)) {
+		dev_err(&pdev->dev, "cannot get resources\n");
+		ret = PTR_ERR(rpc->base);
+		goto error;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+
+	rpc->read_area = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rpc->base)) {
+		dev_err(&pdev->dev, "cannot get resources\n");
+		ret = PTR_ERR(rpc->base);
+		goto error;
+	}
+
+	/* ...get memory */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	rpc->write_area = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(rpc->base)) {
+		dev_err(&pdev->dev, "cannot get resources\n");
+		ret = PTR_ERR(rpc->base);
+		goto error;
+	}
+
+	/* ...get clk */
+	rpc->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(rpc->clk)) {
+		dev_err(&pdev->dev, "cannot get clock\n");
+		ret = PTR_ERR(rpc->clk);
+		goto error;
+	}
+
+	/* ...set max clk rate */
+	if (!own_clk) {
+		ret = clk_set_rate(rpc->clk, max_clk_rate);
+		if (ret) {
+			dev_err(&pdev->dev, "cannot set clock rate\n");
+			goto error;
+		}
+	}
+
+	/* ... enable clk */
+	ret = clk_prepare_enable(rpc->clk);
+	if (ret) {
+		dev_err(&pdev->dev, "cannot prepare clock\n");
+		goto error;
+	}
+
+	/* ...init device */
+	ret = rpc_hw_init(rpc);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "rpc_hw_init error.\n");
+		goto error_clk_disable;
+	}
+
+	/* ...set clk ratio */
+	if (own_clk) {
+		ret = rpc_setup_clk_ratio(rpc, max_clk_rate);
+		if (ret) {
+			dev_err(&pdev->dev, "cannot set clock ratio\n");
+			goto error;
+		}
+	}
+
+	platform_set_drvdata(pdev, rpc);
+
+	ret = spi_nor_scan(nor, NULL, mode);
+	if (ret) {
+		dev_err(&pdev->dev, "spi_nor_scan error.\n");
+		goto error_clk_disable;
+	}
+
+	ret = mtd_device_register(&nor->mtd, NULL, 0);
+	if (ret) {
+		dev_err(&pdev->dev, "mtd_device_register error.\n");
+		goto error_clk_disable;
+	}
+
+	dev_info(&pdev->dev, "probed as %s\n",
+		rpc->mtdtype == MTD_QSPI_1x ? "single" : "dual");
+
+	return 0;
+
+
+error_clk_disable:
+	clk_disable_unprepare(rpc->clk);
+error:
+	return ret;
+}
+
+
+
+
+static int rpc_spi_remove(struct platform_device *pdev)
+{
+	struct rpc_spi *rpc = platform_get_drvdata(pdev);
+
+	/* HW shutdown */
+	clk_disable_unprepare(rpc->clk);
+	mtd_device_unregister(&rpc->spi_nor.mtd);
+	return 0;
+}
+
+
+
+
+
+/* platform driver interface */
+static struct platform_driver rpc_platform_driver = {
+	.probe		= rpc_spi_probe,
+	.remove		= rpc_spi_remove,
+	.driver		= {
+		.owner	= THIS_MODULE,
+		.name	= "rpc",
+		.of_match_table = of_match_ptr(rpc_of_match),
+	},
+};
+
+module_platform_driver(rpc_platform_driver);
+
+
+MODULE_ALIAS("rpc");
+MODULE_AUTHOR("Cogent Embedded Inc. <sources@cogentembedded.com>");
+MODULE_DESCRIPTION("Renesas RPC Driver");
+MODULE_LICENSE("GPL");
-- 
2.7.4

