summaryrefslogtreecommitdiffstats
path: root/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0113-Renesas-RPC-Add-RPC-driver.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0113-Renesas-RPC-Add-RPC-driver.patch')
-rw-r--r--meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0113-Renesas-RPC-Add-RPC-driver.patch1483
1 files changed, 1483 insertions, 0 deletions
diff --git a/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0113-Renesas-RPC-Add-RPC-driver.patch b/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0113-Renesas-RPC-Add-RPC-driver.patch
new file mode 100644
index 0000000..86b2d6c
--- /dev/null
+++ b/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0113-Renesas-RPC-Add-RPC-driver.patch
@@ -0,0 +1,1483 @@
+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
+