Message ID | 1538cadb-7c6a-2c4c-fe8c-960b25286950@cogentembedded.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add Renesas RPC-IF support | expand |
On Thu, 30 May 2019, Sergei Shtylyov wrote: > Add the MFD driver for Renesas RPC-IF which registers either the SPI or > HyperFLash device depending on the contents of the device tree subnode. > It also provides the absract "back end" device APIs that can be used by > the "front end" SPI/MTD drivers to talk to the real hardware. > > Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. > > Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> > > --- > drivers/mfd/Kconfig | 10 > drivers/mfd/Makefile | 1 > drivers/mfd/rpc-if.c | 535 +++++++++++++++++++++++++++++++++++++++++++++ > include/linux/mfd/rpc-if.h | 85 +++++++ > 4 files changed, 631 insertions(+) > > Index: mfd/drivers/mfd/Kconfig > =================================================================== > --- mfd.orig/drivers/mfd/Kconfig > +++ mfd/drivers/mfd/Kconfig > @@ -1002,6 +1002,16 @@ config MFD_RDC321X > southbridge which provides access to GPIOs and Watchdog using the > southbridge PCI device configuration space. > > +config MFD_RPCIF > + tristate "Renesas RPC-IF driver" > + select MFD_CORE > + select REGMAP_MMIO > + depends on ARCH_RENESAS > + help > + This supports Renesas R-Car Gen3 RPC-IF which provides either SPI > + host or HyperFlash. You'll have to select individual components > + under the corresponding menu. > + > config MFD_RT5033 > tristate "Richtek RT5033 Power Management IC" > depends on I2C > Index: mfd/drivers/mfd/Makefile > =================================================================== > --- mfd.orig/drivers/mfd/Makefile > +++ mfd/drivers/mfd/Makefile > @@ -184,6 +184,7 @@ obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) + > obj-$(CONFIG_LPC_SCH) += lpc_sch.o > obj-$(CONFIG_LPC_ICH) += lpc_ich.o > obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o > +obj-$(CONFIG_MFD_RPCIF) += rpc-if.o > obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o > obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o > obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o > Index: mfd/drivers/mfd/rpc-if.c > =================================================================== > --- /dev/null > +++ mfd/drivers/mfd/rpc-if.c > @@ -0,0 +1,535 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Renesas RPC-IF MFD driver > + * > + * Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. > + * Copyright (C) 2019 Macronix International Co., Ltd. > + * Copyright (C) 2019 Cogent Embedded, Inc. > + */ > + > +#include <linux/clk.h> > +#include <linux/io.h> > +#include <linux/mfd/core.h> > +#include <linux/mfd/rpc-if.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/of.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/reset.h> Alphabetical. > +#include <asm/unaligned.h> > + > +#define RPCIF_CMNCR 0x0000 // R/W No C++ comments. > +#define RPCIF_CMNCR_MD BIT(31) > +#define RPCIF_CMNCR_SFDE BIT(24) // undocumented bit but must be set > +#define RPCIF_CMNCR_MOIIO3(val) (((val) & 0x3) << 22) > +#define RPCIF_CMNCR_MOIIO2(val) (((val) & 0x3) << 20) > +#define RPCIF_CMNCR_MOIIO1(val) (((val) & 0x3) << 18) > +#define RPCIF_CMNCR_MOIIO0(val) (((val) & 0x3) << 16) > +#define RPCIF_CMNCR_MOIIO_HIZ (RPCIF_CMNCR_MOIIO0(3) | \ > + RPCIF_CMNCR_MOIIO1(3) | \ > + RPCIF_CMNCR_MOIIO2(3) | RPCIF_CMNCR_MOIIO3(3)) > +#define RPCIF_CMNCR_IO3FV(val) (((val) & 0x3) << 14) // undocumented > +#define RPCIF_CMNCR_IO2FV(val) (((val) & 0x3) << 12) // undocumented > +#define RPCIF_CMNCR_IO0FV(val) (((val) & 0x3) << 8) > +#define RPCIF_CMNCR_IOFV_HIZ (RPCIF_CMNCR_IO0FV(3) | RPCIF_CMNCR_IO2FV(3) | \ > + RPCIF_CMNCR_IO3FV(3)) > +#define RPCIF_CMNCR_BSZ(val) (((val) & 0x3) << 0) > + > +#define RPCIF_SSLDR 0x0004 // R/W > +#define RPCIF_SSLDR_SPNDL(d) (((d) & 0x7) << 16) > +#define RPCIF_SSLDR_SLNDL(d) (((d) & 0x7) << 8) > +#define RPCIF_SSLDR_SCKDL(d) (((d) & 0x7) << 0) > + > +#define RPCIF_DRCR 0x000C // R/W > +#define RPCIF_DRCR_SSLN BIT(24) > +#define RPCIF_DRCR_RBURST(v) ((((v) - 1) & 0x1F) << 16) > +#define RPCIF_DRCR_RCF BIT(9) > +#define RPCIF_DRCR_RBE BIT(8) > +#define RPCIF_DRCR_SSLE BIT(0) > + > +#define RPCIF_DRCMR 0x0010 // R/W > +#define RPCIF_DRCMR_CMD(c) (((c) & 0xFF) << 16) > +#define RPCIF_DRCMR_OCMD(c) (((c) & 0xFF) << 0) > + > +#define RPCIF_DREAR 0x0014 // R/W > +#define RPCIF_DREAR_EAV(c) (((c) & 0xf) << 16) > +#define RPCIF_DREAR_EAC(c) (((c) & 0x7) << 0) > + > +#define RPCIF_DROPR 0x0018 // R/W > + > +#define RPCIF_DRENR 0x001C // R/W > +#define RPCIF_DRENR_CDB(o) (u32)((((o) & 0x3) << 30)) > +#define RPCIF_DRENR_OCDB(o) (((o) & 0x3) << 28) > +#define RPCIF_DRENR_ADB(o) (((o) & 0x3) << 24) > +#define RPCIF_DRENR_OPDB(o) (((o) & 0x3) << 20) > +#define RPCIF_DRENR_DRDB(o) (((o) & 0x3) << 16) > +#define RPCIF_DRENR_DME BIT(15) > +#define RPCIF_DRENR_CDE BIT(14) > +#define RPCIF_DRENR_OCDE BIT(12) > +#define RPCIF_DRENR_ADE(v) (((v) & 0xF) << 8) > +#define RPCIF_DRENR_OPDE(v) (((v) & 0xF) << 4) > + > +#define RPCIF_SMCR 0x0020 // R/W > +#define RPCIF_SMCR_SSLKP BIT(8) > +#define RPCIF_SMCR_SPIRE BIT(2) > +#define RPCIF_SMCR_SPIWE BIT(1) > +#define RPCIF_SMCR_SPIE BIT(0) > + > +#define RPCIF_SMCMR 0x0024 // R/W > +#define RPCIF_SMCMR_CMD(c) (((c) & 0xFF) << 16) > +#define RPCIF_SMCMR_OCMD(c) (((c) & 0xFF) << 0) > + > +#define RPCIF_SMADR 0x0028 // R/W > +#define RPCIF_SMOPR 0x002C // R/W > +#define RPCIF_SMOPR_OPD3(o) (((o) & 0xFF) << 24) > +#define RPCIF_SMOPR_OPD2(o) (((o) & 0xFF) << 16) > +#define RPCIF_SMOPR_OPD1(o) (((o) & 0xFF) << 8) > +#define RPCIF_SMOPR_OPD0(o) (((o) & 0xFF) << 0) > + > +#define RPCIF_SMENR 0x0030 // R/W > +#define RPCIF_SMENR_CDB(o) (((o) & 0x3) << 30) > +#define RPCIF_SMENR_OCDB(o) (((o) & 0x3) << 28) > +#define RPCIF_SMENR_ADB(o) (((o) & 0x3) << 24) > +#define RPCIF_SMENR_OPDB(o) (((o) & 0x3) << 20) > +#define RPCIF_SMENR_SPIDB(o) (((o) & 0x3) << 16) > +#define RPCIF_SMENR_DME BIT(15) > +#define RPCIF_SMENR_CDE BIT(14) > +#define RPCIF_SMENR_OCDE BIT(12) > +#define RPCIF_SMENR_ADE(v) (((v) & 0xF) << 8) > +#define RPCIF_SMENR_OPDE(v) (((v) & 0xF) << 4) > +#define RPCIF_SMENR_SPIDE(v) (((v) & 0xF) << 0) > + > +#define RPCIF_SMRDR0 0x0038 // R > +#define RPCIF_SMRDR1 0x003C // R > +#define RPCIF_SMWDR0 0x0040 // W > +#define RPCIF_SMWDR1 0x0044 // W > + > +#define RPCIF_CMNSR 0x0048 // R > +#define RPCIF_CMNSR_SSLF BIT(1) > +#define RPCIF_CMNSR_TEND BIT(0) > + > +#define RPCIF_DRDMCR 0x0058 // R/W > +#define RPCIF_DMDMCR_DMCYC(v) ((((v) - 1) & 0x1F) << 0) > + > +#define RPCIF_DRDRENR 0x005C // R/W > +#define RPCIF_DRDRENR_HYPE(v) (((v) & 0x7) << 12) > +#define RPCIF_DRDRENR_ADDRE BIT(8) > +#define RPCIF_DRDRENR_OPDRE BIT(4) > +#define RPCIF_DRDRENR_DRDRE BIT(0) > + > +#define RPCIF_SMDMCR 0x0060 // R/W > +#define RPCIF_SMDMCR_DMCYC(v) ((((v) - 1) & 0x1F) << 0) > + > +#define RPCIF_SMDRENR 0x0064 // R/W > +#define RPCIF_SMDRENR_HYPE(v) (((v) & 0x7) << 12) > +#define RPCIF_SMDRENR_ADDRE BIT(8) > +#define RPCIF_SMDRENR_OPDRE BIT(4) > +#define RPCIF_SMDRENR_SPIDRE BIT(0) > + > +#define RPCIF_PHYCNT 0x007C // R/W > +#define RPCIF_PHYCNT_CAL BIT(31) > +#define RPCIF_PHYCNT_OCTA_AA BIT(22) > +#define RPCIF_PHYCNT_OCTA_SA BIT(23) > +#define RPCIF_PHYCNT_EXDS BIT(21) > +#define RPCIF_PHYCNT_OCT BIT(20) > +#define RPCIF_PHYCNT_STRTIM(v) (((v) & 0x7) << 15) > +#define RPCIF_PHYCNT_WBUF2 BIT(4) > +#define RPCIF_PHYCNT_WBUF BIT(2) > +#define RPCIF_PHYCNT_PHYMEM(v) (((v) & 0x3) << 0) > + > +#define RPCIF_PHYOFFSET1 0x0080 // R/W > +#define RPCIF_PHYOFFSET1_DDRTMG(v) (((v) & 0x3) << 28) > +#define RPCIF_PHYOFFSET2 0x0084 // R/W > +#define RPCIF_PHYOFFSET2_OCTTMG(v) (((v) & 0x7) << 8) > + > +#define RPCIF_DIRMAP_SIZE 0x4000000 Can you shift this lot out to a header file please. > +void rpcif_enable_rpm(struct rpcif *rpc) > +{ > + pm_runtime_enable(rpc->dev); > +} > +EXPORT_SYMBOL(rpcif_enable_rpm); > + > +void rpcif_disable_rpm(struct rpcif *rpc) > +{ > + pm_runtime_disable(rpc->dev); > +} > +EXPORT_SYMBOL(rpcif_disable_rpm); Where are you exporting these out to? Why are you seemingly unnecessarily abstracting out the pm_* API? > +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash) > +{ > + pm_runtime_get_sync(rpc->dev); > + > + /* > + * NOTE: The 0x260 are undocumented bits, but they must be set. > + * RPCIF_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, RPCIF_PHYCNT, > + RPCIF_PHYCNT_CAL | RPCIF_PHYCNT_STRTIM(6) | > + RPCIF_PHYCNT_PHYMEM(hyperflash ? 3 : 0) | 0x260); At least #define it, even it it's not documented. > + /* > + * NOTE: The 0x1511144 are undocumented bits, but they must be set > + * for RPCIF_PHYOFFSET1. > + * The 0x31 are undocumented bits, but they must be set > + * for RPCIF_PHYOFFSET2. > + */ > + regmap_write(rpc->regmap, RPCIF_PHYOFFSET1, > + RPCIF_PHYOFFSET1_DDRTMG(3) | 0x1511144); > + regmap_write(rpc->regmap, RPCIF_PHYOFFSET2, 0x31 | > + RPCIF_PHYOFFSET2_OCTTMG(4)); No magic numbers. Please #define them all. > + regmap_write(rpc->regmap, RPCIF_SSLDR, RPCIF_SSLDR_SPNDL(7) | > + RPCIF_SSLDR_SLNDL(7) | RPCIF_SSLDR_SCKDL(7)); > + regmap_write(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD | > + RPCIF_CMNCR_SFDE | RPCIF_CMNCR_MOIIO_HIZ | > + RPCIF_CMNCR_IOFV_HIZ | > + RPCIF_CMNCR_BSZ(hyperflash ? 1 : 0)); > + > + pm_runtime_put(rpc->dev); > +} > +EXPORT_SYMBOL(rpcif_hw_init); > + > +static int wait_msg_xfer_end(struct rpcif *rpc) > +{ > + u32 sts; > + > + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, > + sts & RPCIF_CMNSR_TEND, 0, Aren't you using sts undefined here? > + USEC_PER_SEC); > +} > + > +static u8 rpcif_bits_set(u32 nbytes) > +{ > + nbytes = clamp(nbytes, 1U, 4U); > + > + return GENMASK(3, 4 - nbytes); > +} Looking at all this code from here ... > +void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, > + size_t *len) > +{ > + rpc->enable = 0; > + rpc->command = 0; > + rpc->xferlen = 0; > + rpc->address = 0; > + rpc->ddr = 0; > + > + if (op->cmd.buswidth) { > + rpc->enable |= RPCIF_SMENR_CDE | > + RPCIF_SMENR_CDB(ilog2(op->cmd.buswidth)); > + rpc->command |= RPCIF_SMCMR_CMD(op->cmd.opcode); > + if (op->cmd.ddr) > + rpc->ddr |= RPCIF_SMDRENR_HYPE(0x5); > + } > + if (op->ocmd.buswidth) { > + rpc->enable |= RPCIF_SMENR_OCDE | > + RPCIF_SMENR_OCDB(ilog2(op->ocmd.buswidth)); > + rpc->command |= RPCIF_SMCMR_OCMD(op->ocmd.opcode); > + } > + > + if (op->addr.buswidth) { > + rpc->enable |= RPCIF_SMENR_ADB(ilog2(op->addr.buswidth)); > + if (op->addr.nbytes == 4) > + rpc->enable |= RPCIF_SMENR_ADE(0xf); > + else > + rpc->enable |= RPCIF_SMENR_ADE(0x7); > + if (op->addr.ddr) > + rpc->ddr |= RPCIF_SMDRENR_ADDRE; > + > + if (offs && len) > + rpc->address = *offs; > + else > + rpc->address = op->addr.val; > + } > + > + if (op->dummy.buswidth) { > + rpc->enable |= RPCIF_SMENR_DME; > + rpc->dummy = RPCIF_SMDMCR_DMCYC(op->dummy.nbytes * 8 / > + op->dummy.buswidth); > + } > + > + if (op->option.buswidth) { > + rpc->enable |= RPCIF_SMENR_OPDE( > + rpcif_bits_set(op->option.nbytes)) | > + RPCIF_SMENR_OPDB(ilog2(op->option.buswidth)); > + if (op->option.ddr) > + rpc->ddr |= RPCIF_SMDRENR_OPDRE; > + rpc->option = op->option.val; > + } > + > + if (op->data.buswidth || (offs && len)) { > + switch (op->data.dir) { > + case RPCIF_DATA_IN: > + rpc->smcr = RPCIF_SMCR_SPIRE; > + break; > + case RPCIF_DATA_OUT: > + rpc->smcr = RPCIF_SMCR_SPIWE; > + break; > + default: > + break; > + } > + if (op->data.ddr) > + rpc->ddr |= RPCIF_SMDRENR_SPIDRE; > + > + if (offs && len) { > + rpc->enable |= RPCIF_SMENR_SPIDE(rpcif_bits_set(*len)) | > + RPCIF_SMENR_SPIDB( > + ilog2(op->data.buswidth)); > + rpc->xferlen = *len; > + } else { > + rpc->enable |= > + RPCIF_SMENR_SPIDE( > + rpcif_bits_set(op->data.nbytes)) | > + RPCIF_SMENR_SPIDB(ilog2(op->data.buswidth)); > + rpc->xferlen = op->data.nbytes; > + } > + } > +} > +EXPORT_SYMBOL(rpcif_prepare); > + > +int rpcif_io_xfer(struct rpcif *rpc, const void *tx_buf, void *rx_buf) > +{ > + u32 smenr, smcr, data, pos = 0; > + int ret = 0; > + > + pm_runtime_get_sync(rpc->dev); > + > + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, > + RPCIF_CMNCR_MD); > + regmap_write(rpc->regmap, RPCIF_SMCMR, rpc->command); > + regmap_write(rpc->regmap, RPCIF_SMDMCR, rpc->dummy); > + regmap_write(rpc->regmap, RPCIF_SMADR, rpc->address); > + regmap_write(rpc->regmap, RPCIF_SMDRENR, rpc->ddr); > + smenr = rpc->enable; > + > + if (tx_buf) { > + while (pos < rpc->xferlen) { > + u32 nbytes = rpc->xferlen - pos; > + > + regmap_write(rpc->regmap, RPCIF_SMWDR0, > + get_unaligned((u32 *)(tx_buf + pos))); > + > + smcr = rpc->smcr | RPCIF_SMCR_SPIE; > + > + if (nbytes > 4) { > + nbytes = 4; > + smcr |= RPCIF_SMCR_SSLKP; > + } > + > + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); > + regmap_write(rpc->regmap, RPCIF_SMCR, smcr); > + ret = wait_msg_xfer_end(rpc); > + if (ret) > + goto err_out; > + > + pos += nbytes; > + smenr = rpc->enable & > + ~RPCIF_SMENR_CDE & ~RPCIF_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 & RPCIF_SMENR_ADE(0xf)) && rpc->dirmap) { > + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, > + RPCIF_CMNCR_MD, 0); > + regmap_write(rpc->regmap, RPCIF_DRCR, > + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); > + regmap_write(rpc->regmap, RPCIF_DREAR, > + RPCIF_DREAR_EAC(1)); > + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); > + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); > + regmap_write(rpc->regmap, RPCIF_DROPR, 0); > + regmap_write(rpc->regmap, RPCIF_DRENR, > + smenr & ~RPCIF_SMENR_SPIDE(0xf)); > + memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen); > + regmap_write(rpc->regmap, RPCIF_DRCR, RPCIF_DRCR_RCF); > + } else { > + while (pos < rpc->xferlen) { > + u32 nbytes = rpc->xferlen - pos; > + > + if (nbytes > 4) > + nbytes = 4; > + > + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); > + regmap_write(rpc->regmap, RPCIF_SMCR, > + rpc->smcr | RPCIF_SMCR_SPIE); > + ret = wait_msg_xfer_end(rpc); > + if (ret) > + goto err_out; > + > + regmap_read(rpc->regmap, RPCIF_SMRDR0, &data); > + memcpy(rx_buf + pos, &data, nbytes); > + pos += nbytes; > + > + regmap_write(rpc->regmap, RPCIF_SMADR, > + rpc->address + pos); > + } > + } > + } else { > + regmap_write(rpc->regmap, RPCIF_SMENR, rpc->enable); > + regmap_write(rpc->regmap, RPCIF_SMCR, > + rpc->smcr | RPCIF_SMCR_SPIE); > + ret = wait_msg_xfer_end(rpc); > + if (ret) > + goto err_out; > + } > + > +exit: > + pm_runtime_put(rpc->dev); > + return ret; > + > +err_out: > + ret = reset_control_reset(rpc->rstc); > + goto exit; > +} > +EXPORT_SYMBOL(rpcif_io_xfer); > + > +ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf) > +{ > + loff_t from = offs & (RPCIF_DIRMAP_SIZE - 1); > + size_t size = RPCIF_DIRMAP_SIZE - from; > + int rc; > + > + if (len > size) > + len = size; > + > + rc = pm_runtime_get_sync(rpc->dev); > + > + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, 0); > + regmap_write(rpc->regmap, RPCIF_DRCR, > + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); > + > + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); > + regmap_write(rpc->regmap, RPCIF_DREAR, > + RPCIF_DREAR_EAV(offs >> 25) | RPCIF_DREAR_EAC(1)); > + regmap_write(rpc->regmap, RPCIF_DROPR, rpc->option); > + regmap_write(rpc->regmap, RPCIF_DRENR, > + rpc->enable & ~RPCIF_SMENR_SPIDE(0xF)); > + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); > + regmap_write(rpc->regmap, RPCIF_DRDRENR, rpc->ddr); > + > + memcpy_fromio(buf, rpc->dirmap + from, len); > + > + pm_runtime_put(rpc->dev); > + > + return len; > +} > +EXPORT_SYMBOL(rpcif_dirmap_read); ... to here. Not sure what all this is, but it looks like it doesn't have anything to do with MFD. I suggest you move it to somewhere more appropriate. > +static const struct mfd_cell rpcif_hf_ctlr = { > + .name = "rpcif-hyperflash", > +}; > + > +static const struct mfd_cell rpcif_spi_ctlr = { > + .name = "rpcif-spi", > +}; This looks like a very tenuous use of the MFD API. I suggest that this isn't actually an MFD at all. > +static const struct regmap_range rpcif_volatile_ranges[] = { > + regmap_reg_range(RPCIF_SMRDR0, RPCIF_SMRDR1), > + regmap_reg_range(RPCIF_SMWDR0, RPCIF_SMWDR1), > + regmap_reg_range(RPCIF_CMNSR, RPCIF_CMNSR), > +}; > + > +static const struct regmap_access_table rpcif_volatile_table = { > + .yes_ranges = rpcif_volatile_ranges, > + .n_yes_ranges = ARRAY_SIZE(rpcif_volatile_ranges), > +}; > + > +static const struct regmap_config rpcif_regmap_config = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .fast_io = true, > + .max_register = RPCIF_PHYOFFSET2, > + .volatile_table = &rpcif_volatile_table, > +}; > + > +static int rpcif_probe(struct platform_device *pdev) > +{ > + struct device_node *flash; > + const struct mfd_cell *cell; > + struct resource *res; > + void __iomem *base; > + struct rpcif *rpc; > + > + flash = of_get_next_child(pdev->dev.of_node, NULL); > + if (!flash) { > + dev_warn(&pdev->dev, "no flash node found\n"); > + return -ENODEV; > + } > + > + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > + cell = &rpcif_spi_ctlr; > + } else if (of_device_is_compatible(flash, "cfi-flash")) { > + cell = &rpcif_hf_ctlr; > + } else { > + dev_warn(&pdev->dev, "unknown flash type\n"); > + return -ENODEV; > + } Why not let DT choose which device to probe? > + rpc = devm_kzalloc(&pdev->dev, sizeof(*rpc), GFP_KERNEL); > + if (!rpc) > + return -ENOMEM; > + > + rpc->dev = &pdev->dev; > + > + rpc->clk_rpcif = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(rpc->clk_rpcif)) > + return PTR_ERR(rpc->clk_rpcif); > + > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); > + base = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + rpc->regmap = devm_regmap_init_mmio(&pdev->dev, base, > + &rpcif_regmap_config); > + if (IS_ERR(rpc->regmap)) { > + dev_err(&pdev->dev, > + "failed to init regmap for rpcif, 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; > + > + rpc->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); > + if (IS_ERR(rpc->rstc)) > + return PTR_ERR(rpc->rstc); > + > + platform_set_drvdata(pdev, rpc); > + > + return devm_mfd_add_devices(&pdev->dev, -1, cell, 1, NULL, 0, NULL); > +} > + > +static const struct of_device_id rpcif_of_match[] = { > + { .compatible = "renesas,rcar-gen3-rpcif", }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, rpcif_of_match); > + > +static struct platform_driver rpcif_driver = { > + .probe = rpcif_probe, > + .driver = { > + .name = "rpcif", > + .of_match_table = rpcif_of_match, > + }, > +}; > +module_platform_driver(rpcif_driver); > + > +MODULE_DESCRIPTION("Renesas RPC-IF MFD driver"); > +MODULE_LICENSE("GPL v2"); > Index: mfd/include/linux/mfd/rpc-if.h > =================================================================== > --- /dev/null > +++ mfd/include/linux/mfd/rpc-if.h > @@ -0,0 +1,85 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. > + * Copyright (C) 2019 Macronix International Co., Ltd. > + * > + * R-Car Gen3 RPC-IF MFD driver > + * > + * Author: > + * Mason Yang <masonccyang@mxic.com.tw> > + */ > + > +#ifndef __MFD_RPC_IF_H > +#define __MFD_RPC_IF_H > + > +#include <linux/types.h> > + > +enum rpcif_data_dir { > + RPCIF_NO_DATA, > + RPCIF_DATA_IN, > + RPCIF_DATA_OUT, > +}; > + > +struct rpcif_op { > + struct { > + u8 buswidth; > + u8 opcode; > + bool ddr; > + } cmd, ocmd; > + > + struct { > + u8 nbytes; > + u8 buswidth; > + bool ddr; > + u64 val; > + } addr; > + > + struct { > + u8 nbytes; > + u8 buswidth; > + } dummy; > + > + struct { > + u8 nbytes; > + u8 buswidth; > + bool ddr; > + u32 val; > + } option; > + > + struct { > + u8 buswidth; > + enum rpcif_data_dir dir; > + unsigned int nbytes; > + bool ddr; > + union { > + void *in; > + const void *out; > + } buf; > + } data; > +}; > + > +struct rpcif { > + struct device *dev; > + struct clk *clk_rpcif; > + void __iomem *dirmap; > + struct regmap *regmap; > + struct reset_control *rstc; > + u32 enable; > + u32 command; > + u32 address; > + u32 dummy; > + u32 option; > + u32 ddr; > + u32 smcr; > + u32 xferlen; > +}; > + > +void rpcif_enable_rpm(struct rpcif *rpc); > +void rpcif_disable_rpm(struct rpcif *rpc); > +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash); > +void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, > + size_t *len); > +int rpcif_io_xfer(struct rpcif *rpc, const void *tx_buf, void *rx_buf); > +ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf); > + > +#endif // __MFD_RPC_IF_H
Hello! On 06/12/2019 12:05 PM, Lee Jones wrote: >> Add the MFD driver for Renesas RPC-IF which registers either the SPI or >> HyperFLash device depending on the contents of the device tree subnode. >> It also provides the absract "back end" device APIs that can be used by >> the "front end" SPI/MTD drivers to talk to the real hardware. >> >> Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. >> >> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> [...] >> Index: mfd/drivers/mfd/rpc-if.c >> =================================================================== >> --- /dev/null >> +++ mfd/drivers/mfd/rpc-if.c >> @@ -0,0 +1,535 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * Renesas RPC-IF MFD driver >> + * >> + * Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. >> + * Copyright (C) 2019 Macronix International Co., Ltd. >> + * Copyright (C) 2019 Cogent Embedded, Inc. >> + */ >> + >> +#include <linux/clk.h> >> +#include <linux/io.h> >> +#include <linux/mfd/core.h> >> +#include <linux/mfd/rpc-if.h> >> +#include <linux/module.h> >> +#include <linux/platform_device.h> >> +#include <linux/of.h> >> +#include <linux/pm_runtime.h> >> +#include <linux/regmap.h> >> +#include <linux/reset.h> > > Alphabetical. I thought they were all sorted them but one #include escaped! :-) >> +#include <asm/unaligned.h> >> + >> +#define RPCIF_CMNCR 0x0000 // R/W > > No C++ comments. Well, with the appearance of the SPDX IDs, we thought that //'s will become acceptable as well -- and indeed Mark Brown has requested consistent C++ comments following the SPDX line for my SPI "sub-driver"... But anyway doesn't seem to be codified yet, so I'll have to fix that, grr... :-( [...] >> +#define RPCIF_DIRMAP_SIZE 0x4000000 > > Can you shift this lot out to a header file please. You mean all register #defne's? Why? I'm not intending to use them outside this file. >> +void rpcif_enable_rpm(struct rpcif *rpc) >> +{ >> + pm_runtime_enable(rpc->dev); >> +} >> +EXPORT_SYMBOL(rpcif_enable_rpm); >> + >> +void rpcif_disable_rpm(struct rpcif *rpc) >> +{ >> + pm_runtime_disable(rpc->dev); >> +} >> +EXPORT_SYMBOL(rpcif_disable_rpm); > > Where are you exporting these out to? The "sub-drivers" (SPI/HyperFlash-to-RPC translation drivers). > Why are you seemingly unnecessarily abstracting out the pm_* API? With RPM being otherwise controlled by this driver, I thought that should be consistent. >> +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash) >> +{ >> + pm_runtime_get_sync(rpc->dev); >> + >> + /* >> + * NOTE: The 0x260 are undocumented bits, but they must be set. >> + * RPCIF_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, RPCIF_PHYCNT, >> + RPCIF_PHYCNT_CAL | RPCIF_PHYCNT_STRTIM(6) | >> + RPCIF_PHYCNT_PHYMEM(hyperflash ? 3 : 0) | 0x260); > > At least #define it, even it it's not documented. Do you have an idea how to name such #define? >> + /* >> + * NOTE: The 0x1511144 are undocumented bits, but they must be set >> + * for RPCIF_PHYOFFSET1. >> + * The 0x31 are undocumented bits, but they must be set >> + * for RPCIF_PHYOFFSET2. >> + */ >> + regmap_write(rpc->regmap, RPCIF_PHYOFFSET1, >> + RPCIF_PHYOFFSET1_DDRTMG(3) | 0x1511144); >> + regmap_write(rpc->regmap, RPCIF_PHYOFFSET2, 0x31 | >> + RPCIF_PHYOFFSET2_OCTTMG(4)); > > No magic numbers. Please #define them all. what about aming? [...] >> +static int wait_msg_xfer_end(struct rpcif *rpc) >> +{ >> + u32 sts; >> + >> + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, >> + sts & RPCIF_CMNSR_TEND, 0, > > Aren't you using sts undefined here? No, the macro reads 'sts' from the register first. [...] > Looking at all this code from here ... > >> +void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, >> + size_t *len) >> +{ >> + rpc->enable = 0; >> + rpc->command = 0; >> + rpc->xferlen = 0; >> + rpc->address = 0; >> + rpc->ddr = 0; >> + >> + if (op->cmd.buswidth) { >> + rpc->enable |= RPCIF_SMENR_CDE | >> + RPCIF_SMENR_CDB(ilog2(op->cmd.buswidth)); >> + rpc->command |= RPCIF_SMCMR_CMD(op->cmd.opcode); >> + if (op->cmd.ddr) >> + rpc->ddr |= RPCIF_SMDRENR_HYPE(0x5); >> + } >> + if (op->ocmd.buswidth) { >> + rpc->enable |= RPCIF_SMENR_OCDE | >> + RPCIF_SMENR_OCDB(ilog2(op->ocmd.buswidth)); >> + rpc->command |= RPCIF_SMCMR_OCMD(op->ocmd.opcode); >> + } >> + >> + if (op->addr.buswidth) { >> + rpc->enable |= RPCIF_SMENR_ADB(ilog2(op->addr.buswidth)); >> + if (op->addr.nbytes == 4) >> + rpc->enable |= RPCIF_SMENR_ADE(0xf); >> + else >> + rpc->enable |= RPCIF_SMENR_ADE(0x7); >> + if (op->addr.ddr) >> + rpc->ddr |= RPCIF_SMDRENR_ADDRE; >> + >> + if (offs && len) >> + rpc->address = *offs; >> + else >> + rpc->address = op->addr.val; >> + } >> + >> + if (op->dummy.buswidth) { >> + rpc->enable |= RPCIF_SMENR_DME; >> + rpc->dummy = RPCIF_SMDMCR_DMCYC(op->dummy.nbytes * 8 / >> + op->dummy.buswidth); >> + } >> + >> + if (op->option.buswidth) { >> + rpc->enable |= RPCIF_SMENR_OPDE( >> + rpcif_bits_set(op->option.nbytes)) | >> + RPCIF_SMENR_OPDB(ilog2(op->option.buswidth)); >> + if (op->option.ddr) >> + rpc->ddr |= RPCIF_SMDRENR_OPDRE; >> + rpc->option = op->option.val; >> + } >> + >> + if (op->data.buswidth || (offs && len)) { >> + switch (op->data.dir) { >> + case RPCIF_DATA_IN: >> + rpc->smcr = RPCIF_SMCR_SPIRE; >> + break; >> + case RPCIF_DATA_OUT: >> + rpc->smcr = RPCIF_SMCR_SPIWE; >> + break; >> + default: >> + break; >> + } >> + if (op->data.ddr) >> + rpc->ddr |= RPCIF_SMDRENR_SPIDRE; >> + >> + if (offs && len) { >> + rpc->enable |= RPCIF_SMENR_SPIDE(rpcif_bits_set(*len)) | >> + RPCIF_SMENR_SPIDB( >> + ilog2(op->data.buswidth)); >> + rpc->xferlen = *len; >> + } else { >> + rpc->enable |= >> + RPCIF_SMENR_SPIDE( >> + rpcif_bits_set(op->data.nbytes)) | >> + RPCIF_SMENR_SPIDB(ilog2(op->data.buswidth)); >> + rpc->xferlen = op->data.nbytes; >> + } >> + } >> +} >> +EXPORT_SYMBOL(rpcif_prepare); >> + >> +int rpcif_io_xfer(struct rpcif *rpc, const void *tx_buf, void *rx_buf) >> +{ >> + u32 smenr, smcr, data, pos = 0; >> + int ret = 0; >> + >> + pm_runtime_get_sync(rpc->dev); >> + >> + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, >> + RPCIF_CMNCR_MD); >> + regmap_write(rpc->regmap, RPCIF_SMCMR, rpc->command); >> + regmap_write(rpc->regmap, RPCIF_SMDMCR, rpc->dummy); >> + regmap_write(rpc->regmap, RPCIF_SMADR, rpc->address); >> + regmap_write(rpc->regmap, RPCIF_SMDRENR, rpc->ddr); >> + smenr = rpc->enable; >> + >> + if (tx_buf) { >> + while (pos < rpc->xferlen) { >> + u32 nbytes = rpc->xferlen - pos; >> + >> + regmap_write(rpc->regmap, RPCIF_SMWDR0, >> + get_unaligned((u32 *)(tx_buf + pos))); >> + >> + smcr = rpc->smcr | RPCIF_SMCR_SPIE; >> + >> + if (nbytes > 4) { >> + nbytes = 4; >> + smcr |= RPCIF_SMCR_SSLKP; >> + } >> + >> + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); >> + regmap_write(rpc->regmap, RPCIF_SMCR, smcr); >> + ret = wait_msg_xfer_end(rpc); >> + if (ret) >> + goto err_out; >> + >> + pos += nbytes; >> + smenr = rpc->enable & >> + ~RPCIF_SMENR_CDE & ~RPCIF_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 & RPCIF_SMENR_ADE(0xf)) && rpc->dirmap) { >> + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, >> + RPCIF_CMNCR_MD, 0); >> + regmap_write(rpc->regmap, RPCIF_DRCR, >> + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); >> + regmap_write(rpc->regmap, RPCIF_DREAR, >> + RPCIF_DREAR_EAC(1)); >> + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); >> + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); >> + regmap_write(rpc->regmap, RPCIF_DROPR, 0); >> + regmap_write(rpc->regmap, RPCIF_DRENR, >> + smenr & ~RPCIF_SMENR_SPIDE(0xf)); >> + memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen); >> + regmap_write(rpc->regmap, RPCIF_DRCR, RPCIF_DRCR_RCF); >> + } else { >> + while (pos < rpc->xferlen) { >> + u32 nbytes = rpc->xferlen - pos; >> + >> + if (nbytes > 4) >> + nbytes = 4; >> + >> + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); >> + regmap_write(rpc->regmap, RPCIF_SMCR, >> + rpc->smcr | RPCIF_SMCR_SPIE); >> + ret = wait_msg_xfer_end(rpc); >> + if (ret) >> + goto err_out; >> + >> + regmap_read(rpc->regmap, RPCIF_SMRDR0, &data); >> + memcpy(rx_buf + pos, &data, nbytes); >> + pos += nbytes; >> + >> + regmap_write(rpc->regmap, RPCIF_SMADR, >> + rpc->address + pos); >> + } >> + } >> + } else { >> + regmap_write(rpc->regmap, RPCIF_SMENR, rpc->enable); >> + regmap_write(rpc->regmap, RPCIF_SMCR, >> + rpc->smcr | RPCIF_SMCR_SPIE); >> + ret = wait_msg_xfer_end(rpc); >> + if (ret) >> + goto err_out; >> + } >> + >> +exit: >> + pm_runtime_put(rpc->dev); >> + return ret; >> + >> +err_out: >> + ret = reset_control_reset(rpc->rstc); >> + goto exit; >> +} >> +EXPORT_SYMBOL(rpcif_io_xfer); >> + >> +ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf) >> +{ >> + loff_t from = offs & (RPCIF_DIRMAP_SIZE - 1); >> + size_t size = RPCIF_DIRMAP_SIZE - from; >> + int rc; >> + >> + if (len > size) >> + len = size; >> + >> + rc = pm_runtime_get_sync(rpc->dev); >> + >> + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, 0); >> + regmap_write(rpc->regmap, RPCIF_DRCR, >> + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); >> + >> + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); >> + regmap_write(rpc->regmap, RPCIF_DREAR, >> + RPCIF_DREAR_EAV(offs >> 25) | RPCIF_DREAR_EAC(1)); >> + regmap_write(rpc->regmap, RPCIF_DROPR, rpc->option); >> + regmap_write(rpc->regmap, RPCIF_DRENR, >> + rpc->enable & ~RPCIF_SMENR_SPIDE(0xF)); >> + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); >> + regmap_write(rpc->regmap, RPCIF_DRDRENR, rpc->ddr); >> + >> + memcpy_fromio(buf, rpc->dirmap + from, len); >> + >> + pm_runtime_put(rpc->dev); >> + >> + return len; >> +} >> +EXPORT_SYMBOL(rpcif_dirmap_read); > > ... to here. > > Not sure what all this is, but it looks like it doesn't have anything This is an abstracted "back end" API for the "front end" MTD/SPI drivers. Only this code talks to real RPC-IF hardware. Probably needs some kernel-doc... > to do with MFD. I suggest you move it to somewhere more appropriate. Like where? >> +static const struct mfd_cell rpcif_hf_ctlr = { >> + .name = "rpcif-hyperflash", >> +}; >> + >> +static const struct mfd_cell rpcif_spi_ctlr = { >> + .name = "rpcif-spi", >> +}; > > This looks like a very tenuous use of the MFD API. > > I suggest that this isn't actually an MFD at all. The same hardware supports 2 different physical interfaces, hence the drivers have to comply to 2 different driver frameworks... sounded like MFD to me. :-) [...] >> +static int rpcif_probe(struct platform_device *pdev) >> +{ >> + struct device_node *flash; >> + const struct mfd_cell *cell; >> + struct resource *res; >> + void __iomem *base; >> + struct rpcif *rpc; >> + >> + flash = of_get_next_child(pdev->dev.of_node, NULL); >> + if (!flash) { >> + dev_warn(&pdev->dev, "no flash node found\n"); >> + return -ENODEV; >> + } >> + >> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { >> + cell = &rpcif_spi_ctlr; >> + } else if (of_device_is_compatible(flash, "cfi-flash")) { >> + cell = &rpcif_hf_ctlr; >> + } else { >> + dev_warn(&pdev->dev, "unknown flash type\n"); >> + return -ENODEV; >> + } > > Why not let DT choose which device to probe? It's the DT that decides here. How else would you imagine that? It's the same hardware, just the different physical busses that it commands... [...] MBR, Sergei
On Fri, 14 Jun 2019, Sergei Shtylyov wrote: > On 06/12/2019 12:05 PM, Lee Jones wrote: > > >> Add the MFD driver for Renesas RPC-IF which registers either the SPI or > >> HyperFLash device depending on the contents of the device tree subnode. > >> It also provides the absract "back end" device APIs that can be used by > >> the "front end" SPI/MTD drivers to talk to the real hardware. > >> > >> Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. > >> > >> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> > > [...] > >> Index: mfd/drivers/mfd/rpc-if.c > >> =================================================================== > >> --- /dev/null > >> +++ mfd/drivers/mfd/rpc-if.c > >> @@ -0,0 +1,535 @@ > >> +// SPDX-License-Identifier: GPL-2.0 > >> +/* > >> + * Renesas RPC-IF MFD driver > >> + * > >> + * Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. > >> + * Copyright (C) 2019 Macronix International Co., Ltd. > >> + * Copyright (C) 2019 Cogent Embedded, Inc. > >> + */ > >> + > >> +#include <linux/clk.h> > >> +#include <linux/io.h> > >> +#include <linux/mfd/core.h> > >> +#include <linux/mfd/rpc-if.h> > >> +#include <linux/module.h> > >> +#include <linux/platform_device.h> > >> +#include <linux/of.h> > >> +#include <linux/pm_runtime.h> > >> +#include <linux/regmap.h> > >> +#include <linux/reset.h> > > > > Alphabetical. > > I thought they were all sorted them but one #include escaped! :-) > > >> +#include <asm/unaligned.h> > >> + > >> +#define RPCIF_CMNCR 0x0000 // R/W > > > > No C++ comments. > > Well, with the appearance of the SPDX IDs, we thought that //'s will become > acceptable as well -- and indeed Mark Brown has requested consistent C++ comments > following the SPDX line for my SPI "sub-driver"... > But anyway doesn't seem to be codified yet, so I'll have to fix that, grr... :-( He means in the header comment only. This does not open the flood gates for C++ comments throughout the kernel. > [...] > >> +#define RPCIF_DIRMAP_SIZE 0x4000000 > > > > Can you shift this lot out to a header file please. > > You mean all register #defne's? Why? I'm not intending to use them outside > this file. Because its 10's of lines of cruft. People won't want to wade through that to get to real functional C code every time they open up this file. You already have a header file, please use it. > >> +void rpcif_enable_rpm(struct rpcif *rpc) > >> +{ > >> + pm_runtime_enable(rpc->dev); > >> +} > >> +EXPORT_SYMBOL(rpcif_enable_rpm); > >> + > >> +void rpcif_disable_rpm(struct rpcif *rpc) > >> +{ > >> + pm_runtime_disable(rpc->dev); > >> +} > >> +EXPORT_SYMBOL(rpcif_disable_rpm); > > > > Where are you exporting these out to? > > The "sub-drivers" (SPI/HyperFlash-to-RPC translation drivers). > > > Why are you seemingly unnecessarily abstracting out the pm_* API? > > With RPM being otherwise controlled by this driver, I thought that should be > consistent. Just use the pm_runtime_*() API directly. > >> +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash) > >> +{ > >> + pm_runtime_get_sync(rpc->dev); > >> + > >> + /* > >> + * NOTE: The 0x260 are undocumented bits, but they must be set. > >> + * RPCIF_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, RPCIF_PHYCNT, > >> + RPCIF_PHYCNT_CAL | RPCIF_PHYCNT_STRTIM(6) | > >> + RPCIF_PHYCNT_PHYMEM(hyperflash ? 3 : 0) | 0x260); > > > > At least #define it, even it it's not documented. > > Do you have an idea how to name such #define? How did you find out that they must be set? How did you find out the value? What happens if they are not set? > >> + /* > >> + * NOTE: The 0x1511144 are undocumented bits, but they must be set > >> + * for RPCIF_PHYOFFSET1. > >> + * The 0x31 are undocumented bits, but they must be set > >> + * for RPCIF_PHYOFFSET2. > >> + */ > >> + regmap_write(rpc->regmap, RPCIF_PHYOFFSET1, > >> + RPCIF_PHYOFFSET1_DDRTMG(3) | 0x1511144); > >> + regmap_write(rpc->regmap, RPCIF_PHYOFFSET2, 0x31 | > >> + RPCIF_PHYOFFSET2_OCTTMG(4)); > > > > No magic numbers. Please #define them all. > > what about aming? I'd be happy to help, but I need some more information (see above). > [...] > >> +static int wait_msg_xfer_end(struct rpcif *rpc) > >> +{ > >> + u32 sts; > >> + > >> + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, > >> + sts & RPCIF_CMNSR_TEND, 0, > > > > Aren't you using sts undefined here? > > No, the macro reads 'sts' from the register first. That's confusing and ugly. Please re-write it to the code is clear and easy to read/maintain. > [...] > > Looking at all this code from here ... > > > >> +void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, > >> + size_t *len) > >> +{ > >> + rpc->enable = 0; > >> + rpc->command = 0; > >> + rpc->xferlen = 0; > >> + rpc->address = 0; > >> + rpc->ddr = 0; > >> + > >> + if (op->cmd.buswidth) { > >> + rpc->enable |= RPCIF_SMENR_CDE | > >> + RPCIF_SMENR_CDB(ilog2(op->cmd.buswidth)); > >> + rpc->command |= RPCIF_SMCMR_CMD(op->cmd.opcode); > >> + if (op->cmd.ddr) > >> + rpc->ddr |= RPCIF_SMDRENR_HYPE(0x5); > >> + } > >> + if (op->ocmd.buswidth) { > >> + rpc->enable |= RPCIF_SMENR_OCDE | > >> + RPCIF_SMENR_OCDB(ilog2(op->ocmd.buswidth)); > >> + rpc->command |= RPCIF_SMCMR_OCMD(op->ocmd.opcode); > >> + } > >> + > >> + if (op->addr.buswidth) { > >> + rpc->enable |= RPCIF_SMENR_ADB(ilog2(op->addr.buswidth)); > >> + if (op->addr.nbytes == 4) > >> + rpc->enable |= RPCIF_SMENR_ADE(0xf); > >> + else > >> + rpc->enable |= RPCIF_SMENR_ADE(0x7); > >> + if (op->addr.ddr) > >> + rpc->ddr |= RPCIF_SMDRENR_ADDRE; > >> + > >> + if (offs && len) > >> + rpc->address = *offs; > >> + else > >> + rpc->address = op->addr.val; > >> + } > >> + > >> + if (op->dummy.buswidth) { > >> + rpc->enable |= RPCIF_SMENR_DME; > >> + rpc->dummy = RPCIF_SMDMCR_DMCYC(op->dummy.nbytes * 8 / > >> + op->dummy.buswidth); > >> + } > >> + > >> + if (op->option.buswidth) { > >> + rpc->enable |= RPCIF_SMENR_OPDE( > >> + rpcif_bits_set(op->option.nbytes)) | > >> + RPCIF_SMENR_OPDB(ilog2(op->option.buswidth)); > >> + if (op->option.ddr) > >> + rpc->ddr |= RPCIF_SMDRENR_OPDRE; > >> + rpc->option = op->option.val; > >> + } > >> + > >> + if (op->data.buswidth || (offs && len)) { > >> + switch (op->data.dir) { > >> + case RPCIF_DATA_IN: > >> + rpc->smcr = RPCIF_SMCR_SPIRE; > >> + break; > >> + case RPCIF_DATA_OUT: > >> + rpc->smcr = RPCIF_SMCR_SPIWE; > >> + break; > >> + default: > >> + break; > >> + } > >> + if (op->data.ddr) > >> + rpc->ddr |= RPCIF_SMDRENR_SPIDRE; > >> + > >> + if (offs && len) { > >> + rpc->enable |= RPCIF_SMENR_SPIDE(rpcif_bits_set(*len)) | > >> + RPCIF_SMENR_SPIDB( > >> + ilog2(op->data.buswidth)); > >> + rpc->xferlen = *len; > >> + } else { > >> + rpc->enable |= > >> + RPCIF_SMENR_SPIDE( > >> + rpcif_bits_set(op->data.nbytes)) | > >> + RPCIF_SMENR_SPIDB(ilog2(op->data.buswidth)); > >> + rpc->xferlen = op->data.nbytes; > >> + } > >> + } > >> +} > >> +EXPORT_SYMBOL(rpcif_prepare); > >> + > >> +int rpcif_io_xfer(struct rpcif *rpc, const void *tx_buf, void *rx_buf) > >> +{ > >> + u32 smenr, smcr, data, pos = 0; > >> + int ret = 0; > >> + > >> + pm_runtime_get_sync(rpc->dev); > >> + > >> + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, > >> + RPCIF_CMNCR_MD); > >> + regmap_write(rpc->regmap, RPCIF_SMCMR, rpc->command); > >> + regmap_write(rpc->regmap, RPCIF_SMDMCR, rpc->dummy); > >> + regmap_write(rpc->regmap, RPCIF_SMADR, rpc->address); > >> + regmap_write(rpc->regmap, RPCIF_SMDRENR, rpc->ddr); > >> + smenr = rpc->enable; > >> + > >> + if (tx_buf) { > >> + while (pos < rpc->xferlen) { > >> + u32 nbytes = rpc->xferlen - pos; > >> + > >> + regmap_write(rpc->regmap, RPCIF_SMWDR0, > >> + get_unaligned((u32 *)(tx_buf + pos))); > >> + > >> + smcr = rpc->smcr | RPCIF_SMCR_SPIE; > >> + > >> + if (nbytes > 4) { > >> + nbytes = 4; > >> + smcr |= RPCIF_SMCR_SSLKP; > >> + } > >> + > >> + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); > >> + regmap_write(rpc->regmap, RPCIF_SMCR, smcr); > >> + ret = wait_msg_xfer_end(rpc); > >> + if (ret) > >> + goto err_out; > >> + > >> + pos += nbytes; > >> + smenr = rpc->enable & > >> + ~RPCIF_SMENR_CDE & ~RPCIF_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 & RPCIF_SMENR_ADE(0xf)) && rpc->dirmap) { > >> + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, > >> + RPCIF_CMNCR_MD, 0); > >> + regmap_write(rpc->regmap, RPCIF_DRCR, > >> + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); > >> + regmap_write(rpc->regmap, RPCIF_DREAR, > >> + RPCIF_DREAR_EAC(1)); > >> + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); > >> + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); > >> + regmap_write(rpc->regmap, RPCIF_DROPR, 0); > >> + regmap_write(rpc->regmap, RPCIF_DRENR, > >> + smenr & ~RPCIF_SMENR_SPIDE(0xf)); > >> + memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen); > >> + regmap_write(rpc->regmap, RPCIF_DRCR, RPCIF_DRCR_RCF); > >> + } else { > >> + while (pos < rpc->xferlen) { > >> + u32 nbytes = rpc->xferlen - pos; > >> + > >> + if (nbytes > 4) > >> + nbytes = 4; > >> + > >> + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); > >> + regmap_write(rpc->regmap, RPCIF_SMCR, > >> + rpc->smcr | RPCIF_SMCR_SPIE); > >> + ret = wait_msg_xfer_end(rpc); > >> + if (ret) > >> + goto err_out; > >> + > >> + regmap_read(rpc->regmap, RPCIF_SMRDR0, &data); > >> + memcpy(rx_buf + pos, &data, nbytes); > >> + pos += nbytes; > >> + > >> + regmap_write(rpc->regmap, RPCIF_SMADR, > >> + rpc->address + pos); > >> + } > >> + } > >> + } else { > >> + regmap_write(rpc->regmap, RPCIF_SMENR, rpc->enable); > >> + regmap_write(rpc->regmap, RPCIF_SMCR, > >> + rpc->smcr | RPCIF_SMCR_SPIE); > >> + ret = wait_msg_xfer_end(rpc); > >> + if (ret) > >> + goto err_out; > >> + } > >> + > >> +exit: > >> + pm_runtime_put(rpc->dev); > >> + return ret; > >> + > >> +err_out: > >> + ret = reset_control_reset(rpc->rstc); > >> + goto exit; > >> +} > >> +EXPORT_SYMBOL(rpcif_io_xfer); > >> + > >> +ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf) > >> +{ > >> + loff_t from = offs & (RPCIF_DIRMAP_SIZE - 1); > >> + size_t size = RPCIF_DIRMAP_SIZE - from; > >> + int rc; > >> + > >> + if (len > size) > >> + len = size; > >> + > >> + rc = pm_runtime_get_sync(rpc->dev); > >> + > >> + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, 0); > >> + regmap_write(rpc->regmap, RPCIF_DRCR, > >> + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); > >> + > >> + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); > >> + regmap_write(rpc->regmap, RPCIF_DREAR, > >> + RPCIF_DREAR_EAV(offs >> 25) | RPCIF_DREAR_EAC(1)); > >> + regmap_write(rpc->regmap, RPCIF_DROPR, rpc->option); > >> + regmap_write(rpc->regmap, RPCIF_DRENR, > >> + rpc->enable & ~RPCIF_SMENR_SPIDE(0xF)); > >> + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); > >> + regmap_write(rpc->regmap, RPCIF_DRDRENR, rpc->ddr); > >> + > >> + memcpy_fromio(buf, rpc->dirmap + from, len); > >> + > >> + pm_runtime_put(rpc->dev); > >> + > >> + return len; > >> +} > >> +EXPORT_SYMBOL(rpcif_dirmap_read); > > > > ... to here. > > > > Not sure what all this is, but it looks like it doesn't have anything > > This is an abstracted "back end" API for the "front end" MTD/SPI drivers. > Only this code talks to real RPC-IF hardware. Probably needs some kernel-doc... I suggest this needs moving out to a suitable subsystem. Possibly MTD. > > to do with MFD. I suggest you move it to somewhere more appropriate. > > Like where? MTD? > >> +static const struct mfd_cell rpcif_hf_ctlr = { > >> + .name = "rpcif-hyperflash", > >> +}; > >> + > >> +static const struct mfd_cell rpcif_spi_ctlr = { > >> + .name = "rpcif-spi", > >> +}; > > > > This looks like a very tenuous use of the MFD API. > > > > I suggest that this isn't actually an MFD at all. > > The same hardware supports 2 different physical interfaces, hence > the drivers have to comply to 2 different driver frameworks... sounded > like MFD to me. :-) Not to me. This appears to be some kind of 'mode selector' for an MTD device. Lots of drivers have multiple ways to control them - they are not all MFDs. > [...] > >> +static int rpcif_probe(struct platform_device *pdev) > >> +{ > >> + struct device_node *flash; > >> + const struct mfd_cell *cell; > >> + struct resource *res; > >> + void __iomem *base; > >> + struct rpcif *rpc; > >> + > >> + flash = of_get_next_child(pdev->dev.of_node, NULL); > >> + if (!flash) { > >> + dev_warn(&pdev->dev, "no flash node found\n"); > >> + return -ENODEV; > >> + } > >> + > >> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > >> + cell = &rpcif_spi_ctlr; > >> + } else if (of_device_is_compatible(flash, "cfi-flash")) { > >> + cell = &rpcif_hf_ctlr; > >> + } else { > >> + dev_warn(&pdev->dev, "unknown flash type\n"); > >> + return -ENODEV; > >> + } > > > > Why not let DT choose which device to probe? > > It's the DT that decides here. How else would you imagine that? > It's the same hardware, just the different physical busses that it > commands... DT is not deciding here. This driver is deciding based on the information contained in the tables - very different thing. Why not just let the OF framework probe the correct device i.e. let it parse the 2 compatible strings and let it match the correct driver for the device?
Hi Lee, Thanks for your comments! On Mon, Jun 17, 2019 at 11:31 AM Lee Jones <lee.jones@linaro.org> wrote: > On Fri, 14 Jun 2019, Sergei Shtylyov wrote: > > On 06/12/2019 12:05 PM, Lee Jones wrote: > > >> +static const struct mfd_cell rpcif_hf_ctlr = { > > >> + .name = "rpcif-hyperflash", > > >> +}; > > >> + > > >> +static const struct mfd_cell rpcif_spi_ctlr = { > > >> + .name = "rpcif-spi", > > >> +}; > > > > > > This looks like a very tenuous use of the MFD API. > > > > > > I suggest that this isn't actually an MFD at all. > > > > The same hardware supports 2 different physical interfaces, hence > > the drivers have to comply to 2 different driver frameworks... sounded > > like MFD to me. :-) > > Not to me. > > This appears to be some kind of 'mode selector' for an MTD device. ... for either an SPI or MTD device. > Lots of drivers have multiple ways to control them - they are not all > MFDs. So where to reside the common part? drivers/platform/renesas/? > > [...] > > >> +static int rpcif_probe(struct platform_device *pdev) > > >> +{ > > >> + struct device_node *flash; > > >> + const struct mfd_cell *cell; > > >> + struct resource *res; > > >> + void __iomem *base; > > >> + struct rpcif *rpc; > > >> + > > >> + flash = of_get_next_child(pdev->dev.of_node, NULL); > > >> + if (!flash) { > > >> + dev_warn(&pdev->dev, "no flash node found\n"); > > >> + return -ENODEV; > > >> + } > > >> + > > >> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > > >> + cell = &rpcif_spi_ctlr; > > >> + } else if (of_device_is_compatible(flash, "cfi-flash")) { > > >> + cell = &rpcif_hf_ctlr; > > >> + } else { > > >> + dev_warn(&pdev->dev, "unknown flash type\n"); > > >> + return -ENODEV; > > >> + } > > > > > > Why not let DT choose which device to probe? > > > > It's the DT that decides here. How else would you imagine that? > > It's the same hardware, just the different physical busses that it > > commands... > > DT is not deciding here. This driver is deciding based on the > information contained in the tables - very different thing. > > Why not just let the OF framework probe the correct device i.e. let it > parse the 2 compatible strings and let it match the correct driver for > the device? The OF framework matches against the RPC-IF node, which is a single hardware type, hence has a fixed compatible value. The mode depends on the subnode in DT, which is something the OF framework doesn't match against, so the driver itself has to check the subnode's compatible value. DT describes hardware, not software (Linux subsystem boundary) policy. I think you could have two drivers (SPI and MFD) each matching against the same compatible value, with .probe() functions returning -ENODEV if the subnode doesn't have the appropriate compatible value. However, (1) I don't know how well that would play with module autoloading based on of_device_id, and (2) that still leaves the question where to put the shared code. Thanks! Gr{oetje,eeting}s, Geert
On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > On Mon, Jun 17, 2019 at 11:31 AM Lee Jones <lee.jones@linaro.org> wrote: > > On Fri, 14 Jun 2019, Sergei Shtylyov wrote: > > > On 06/12/2019 12:05 PM, Lee Jones wrote: > > > >> +static const struct mfd_cell rpcif_hf_ctlr = { > > > >> + .name = "rpcif-hyperflash", > > > >> +}; > > > >> + > > > >> +static const struct mfd_cell rpcif_spi_ctlr = { > > > >> + .name = "rpcif-spi", > > > >> +}; > > > > > > > > This looks like a very tenuous use of the MFD API. > > > > > > > > I suggest that this isn't actually an MFD at all. > > > > > > The same hardware supports 2 different physical interfaces, hence > > > the drivers have to comply to 2 different driver frameworks... sounded > > > like MFD to me. :-) > > > > Not to me. > > > > This appears to be some kind of 'mode selector' for an MTD device. > > ... for either an SPI or MTD device. Okay, so I think I misunderstood the device. I was under the impression that it was a flash memory device where the only difference was the interface by which it is controlled? > > Lots of drivers have multiple ways to control them - they are not all > > MFDs. > > So where to reside the common part? drivers/platform/renesas/? That does not make sense, since this is not a platform controller. > > > [...] > > > >> +static int rpcif_probe(struct platform_device *pdev) > > > >> +{ > > > >> + struct device_node *flash; > > > >> + const struct mfd_cell *cell; > > > >> + struct resource *res; > > > >> + void __iomem *base; > > > >> + struct rpcif *rpc; > > > >> + > > > >> + flash = of_get_next_child(pdev->dev.of_node, NULL); > > > >> + if (!flash) { > > > >> + dev_warn(&pdev->dev, "no flash node found\n"); > > > >> + return -ENODEV; > > > >> + } > > > >> + > > > >> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > > > >> + cell = &rpcif_spi_ctlr; > > > >> + } else if (of_device_is_compatible(flash, "cfi-flash")) { > > > >> + cell = &rpcif_hf_ctlr; > > > >> + } else { > > > >> + dev_warn(&pdev->dev, "unknown flash type\n"); > > > >> + return -ENODEV; > > > >> + } > > > > > > > > Why not let DT choose which device to probe? > > > > > > It's the DT that decides here. How else would you imagine that? > > > It's the same hardware, just the different physical busses that it > > > commands... > > > > DT is not deciding here. This driver is deciding based on the > > information contained in the tables - very different thing. > > > > Why not just let the OF framework probe the correct device i.e. let it > > parse the 2 compatible strings and let it match the correct driver for > > the device? > > The OF framework matches against the RPC-IF node, which is a single > hardware type, hence has a fixed compatible value. > The mode depends on the subnode in DT, which is something the OF > framework doesn't match against, so the driver itself has to check the > subnode's compatible value. I can see how it has been implemented. It is that which I was questioning. > DT describes hardware, not software (Linux subsystem boundary) policy. So is an RPC-IF a real hardware device. Can you share the datasheet? > I think you could have two drivers (SPI and MFD) each matching against > the same compatible value, with .probe() functions returning -ENODEV No, don't do that. > if the subnode doesn't have the appropriate compatible value. > However, (1) I don't know how well that would play with module > autoloading based on of_device_id, and (2) that still leaves the question > where to put the shared code. Other than the SPI driver in this set (which I have now looked at), what else uses the MFD "back-end"?
Hi Lee, On Tue, Jun 18, 2019 at 11:20 AM Lee Jones <lee.jones@linaro.org> wrote: > On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > > On Mon, Jun 17, 2019 at 11:31 AM Lee Jones <lee.jones@linaro.org> wrote: > > > On Fri, 14 Jun 2019, Sergei Shtylyov wrote: > > > > On 06/12/2019 12:05 PM, Lee Jones wrote: > > > > >> +static const struct mfd_cell rpcif_hf_ctlr = { > > > > >> + .name = "rpcif-hyperflash", > > > > >> +}; > > > > >> + > > > > >> +static const struct mfd_cell rpcif_spi_ctlr = { > > > > >> + .name = "rpcif-spi", > > > > >> +}; > > > > > > > > > > This looks like a very tenuous use of the MFD API. > > > > > > > > > > I suggest that this isn't actually an MFD at all. > > > > > > > > The same hardware supports 2 different physical interfaces, hence > > > > the drivers have to comply to 2 different driver frameworks... sounded > > > > like MFD to me. :-) > > > > > > Not to me. > > > > > > This appears to be some kind of 'mode selector' for an MTD device. > > > > ... for either an SPI or MTD device. > > Okay, so I think I misunderstood the device. I was under the > impression that it was a flash memory device where the only difference > was the interface by which it is controlled? > > > > Lots of drivers have multiple ways to control them - they are not all > > > MFDs. > > > > So where to reside the common part? drivers/platform/renesas/? > > That does not make sense, since this is not a platform controller. > > > > > [...] > > > > >> +static int rpcif_probe(struct platform_device *pdev) > > > > >> +{ > > > > >> + struct device_node *flash; > > > > >> + const struct mfd_cell *cell; > > > > >> + struct resource *res; > > > > >> + void __iomem *base; > > > > >> + struct rpcif *rpc; > > > > >> + > > > > >> + flash = of_get_next_child(pdev->dev.of_node, NULL); > > > > >> + if (!flash) { > > > > >> + dev_warn(&pdev->dev, "no flash node found\n"); > > > > >> + return -ENODEV; > > > > >> + } > > > > >> + > > > > >> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > > > > >> + cell = &rpcif_spi_ctlr; > > > > >> + } else if (of_device_is_compatible(flash, "cfi-flash")) { > > > > >> + cell = &rpcif_hf_ctlr; > > > > >> + } else { > > > > >> + dev_warn(&pdev->dev, "unknown flash type\n"); > > > > >> + return -ENODEV; > > > > >> + } > > > > > > > > > > Why not let DT choose which device to probe? > > > > > > > > It's the DT that decides here. How else would you imagine that? > > > > It's the same hardware, just the different physical busses that it > > > > commands... > > > > > > DT is not deciding here. This driver is deciding based on the > > > information contained in the tables - very different thing. > > > > > > Why not just let the OF framework probe the correct device i.e. let it > > > parse the 2 compatible strings and let it match the correct driver for > > > the device? > > > > The OF framework matches against the RPC-IF node, which is a single > > hardware type, hence has a fixed compatible value. > > The mode depends on the subnode in DT, which is something the OF > > framework doesn't match against, so the driver itself has to check the > > subnode's compatible value. > > I can see how it has been implemented. > > It is that which I was questioning. > > > DT describes hardware, not software (Linux subsystem boundary) policy. > > So is an RPC-IF a real hardware device. Can you share the datasheet? Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is not yet public. However, a very similar hardware block is present in the RZ/A2M SoC. Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group User’s Manual: Hardware", which you can download from https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents Thanks! Gr{oetje,eeting}s, Geert
On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > On Tue, Jun 18, 2019 at 11:20 AM Lee Jones <lee.jones@linaro.org> wrote: > > On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > > > On Mon, Jun 17, 2019 at 11:31 AM Lee Jones <lee.jones@linaro.org> wrote: > > > > On Fri, 14 Jun 2019, Sergei Shtylyov wrote: > > > > > On 06/12/2019 12:05 PM, Lee Jones wrote: > > > > > >> +static const struct mfd_cell rpcif_hf_ctlr = { > > > > > >> + .name = "rpcif-hyperflash", > > > > > >> +}; > > > > > >> + > > > > > >> +static const struct mfd_cell rpcif_spi_ctlr = { > > > > > >> + .name = "rpcif-spi", > > > > > >> +}; > > > > > > > > > > > > This looks like a very tenuous use of the MFD API. > > > > > > > > > > > > I suggest that this isn't actually an MFD at all. > > > > > > > > > > The same hardware supports 2 different physical interfaces, hence > > > > > the drivers have to comply to 2 different driver frameworks... sounded > > > > > like MFD to me. :-) > > > > > > > > Not to me. > > > > > > > > This appears to be some kind of 'mode selector' for an MTD device. > > > > > > ... for either an SPI or MTD device. > > > > Okay, so I think I misunderstood the device. I was under the > > impression that it was a flash memory device where the only difference > > was the interface by which it is controlled? > > > > > > Lots of drivers have multiple ways to control them - they are not all > > > > MFDs. > > > > > > So where to reside the common part? drivers/platform/renesas/? > > > > That does not make sense, since this is not a platform controller. > > > > > > > [...] > > > > > >> +static int rpcif_probe(struct platform_device *pdev) > > > > > >> +{ > > > > > >> + struct device_node *flash; > > > > > >> + const struct mfd_cell *cell; > > > > > >> + struct resource *res; > > > > > >> + void __iomem *base; > > > > > >> + struct rpcif *rpc; > > > > > >> + > > > > > >> + flash = of_get_next_child(pdev->dev.of_node, NULL); > > > > > >> + if (!flash) { > > > > > >> + dev_warn(&pdev->dev, "no flash node found\n"); > > > > > >> + return -ENODEV; > > > > > >> + } > > > > > >> + > > > > > >> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > > > > > >> + cell = &rpcif_spi_ctlr; > > > > > >> + } else if (of_device_is_compatible(flash, "cfi-flash")) { > > > > > >> + cell = &rpcif_hf_ctlr; > > > > > >> + } else { > > > > > >> + dev_warn(&pdev->dev, "unknown flash type\n"); > > > > > >> + return -ENODEV; > > > > > >> + } > > > > > > > > > > > > Why not let DT choose which device to probe? > > > > > > > > > > It's the DT that decides here. How else would you imagine that? > > > > > It's the same hardware, just the different physical busses that it > > > > > commands... > > > > > > > > DT is not deciding here. This driver is deciding based on the > > > > information contained in the tables - very different thing. > > > > > > > > Why not just let the OF framework probe the correct device i.e. let it > > > > parse the 2 compatible strings and let it match the correct driver for > > > > the device? > > > > > > The OF framework matches against the RPC-IF node, which is a single > > > hardware type, hence has a fixed compatible value. > > > The mode depends on the subnode in DT, which is something the OF > > > framework doesn't match against, so the driver itself has to check the > > > subnode's compatible value. > > > > I can see how it has been implemented. > > > > It is that which I was questioning. > > > > > DT describes hardware, not software (Linux subsystem boundary) policy. > > > > So is an RPC-IF a real hardware device. Can you share the datasheet? > > Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is > not yet public. When will it be public? Do you have access to it? Maybe someone who does can help with the magic number definitions. > However, a very similar hardware block is present in the RZ/A2M SoC. > Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group > User’s Manual: Hardware", which you can download from > https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents "The SPI multi I/O bus controller enables the direct connection of serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory devices to this LSI chip. This module allows the connected serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory devices to be accessed by reading the external address space, or using Manual mode to transmit and receive data." Looks like a flash device to me. Can the SPI portion be used to connect generic SPI devices?
Hi Lee, On Tue, Jun 18, 2019 at 11:57 AM Lee Jones <lee.jones@linaro.org> wrote: > On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > > On Tue, Jun 18, 2019 at 11:20 AM Lee Jones <lee.jones@linaro.org> wrote: > > > So is an RPC-IF a real hardware device. Can you share the datasheet? > > > > Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is > > not yet public. > > When will it be public? Dunno. RZ/G1 documentation became public a few months after the SoC release. > Do you have access to it? Yes I do. > > However, a very similar hardware block is present in the RZ/A2M SoC. > > Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group > > User’s Manual: Hardware", which you can download from > > https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents > > "The SPI multi I/O bus controller enables the direct connection of > serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory > devices to this LSI chip. > > This module allows the connected serial flash, OctaFlashTM, XccelaTM > flash, or HyperFlashTM memory devices to be accessed by reading the > external address space, or using Manual mode to transmit and receive > data." > > Looks like a flash device to me. The external address space is a small window. > Can the SPI portion be used to connect generic SPI devices? I'll defer that to the people who worked on the driver... Gr{oetje,eeting}s, Geert
On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > Hi Lee, > > On Tue, Jun 18, 2019 at 11:57 AM Lee Jones <lee.jones@linaro.org> wrote: > > On Tue, 18 Jun 2019, Geert Uytterhoeven wrote: > > > On Tue, Jun 18, 2019 at 11:20 AM Lee Jones <lee.jones@linaro.org> wrote: > > > > So is an RPC-IF a real hardware device. Can you share the datasheet? > > > > > > Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is > > > not yet public. > > > > When will it be public? > > Dunno. RZ/G1 documentation became public a few months after the SoC > release. > > > Do you have access to it? > > Yes I do. Great. Maybe you can help Sergei with his 'undocumented bits' issue. > > > However, a very similar hardware block is present in the RZ/A2M SoC. > > > Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group > > > User’s Manual: Hardware", which you can download from > > > https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents > > > > "The SPI multi I/O bus controller enables the direct connection of > > serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory > > devices to this LSI chip. > > > > This module allows the connected serial flash, OctaFlashTM, XccelaTM > > flash, or HyperFlashTM memory devices to be accessed by reading the > > external address space, or using Manual mode to transmit and receive > > data." > > > > Looks like a flash device to me. > > The external address space is a small window. > > > Can the SPI portion be used to connect generic SPI devices? > > I'll defer that to the people who worked on the driver... ...
Hello! On 06/18/2019 02:34 PM, Lee Jones wrote: >>>>> So is an RPC-IF a real hardware device. Can you share the datasheet? >>>> >>>> Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is >>>> not yet public. >>> >>> When will it be public? >> >> Dunno. RZ/G1 documentation became public a few months after the SoC >> release. >> >>> Do you have access to it? >> >> Yes I do. > > Great. Maybe you can help Sergei with his 'undocumented bits' issue. You're an optimist. :-) I think I have the same gen3 manual v1.50 as Geert. It won't help as the bits that constitute the magic numbers in the driver are not described (not even the default values are listed for the most of them). >>>> However, a very similar hardware block is present in the RZ/A2M SoC. >>>> Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group >>>> User’s Manual: Hardware", which you can download from >>>> https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents >>> >>> "The SPI multi I/O bus controller enables the direct connection of >>> serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory >>> devices to this LSI chip. >>> >>> This module allows the connected serial flash, OctaFlashTM, XccelaTM >>> flash, or HyperFlashTM memory devices to be accessed by reading the >>> external address space, or using Manual mode to transmit and receive >>> data." >>> >>> Looks like a flash device to me. >> >> The external address space is a small window. Yeah, it only provides 64 MiB window into the flash chip. >>> Can the SPI portion be used to connect generic SPI devices? >> >> I'll defer that to the people who worked on the driver... Yes. Or at least it should, looking at the driver code... MBR, Sergei
On 06/18/2019 12:57 PM, Lee Jones wrote: [...] >>>>>>>> +static int rpcif_probe(struct platform_device *pdev) >>>>>>>> +{ >>>>>>>> + struct device_node *flash; >>>>>>>> + const struct mfd_cell *cell; >>>>>>>> + struct resource *res; >>>>>>>> + void __iomem *base; >>>>>>>> + struct rpcif *rpc; >>>>>>>> + >>>>>>>> + flash = of_get_next_child(pdev->dev.of_node, NULL); >>>>>>>> + if (!flash) { >>>>>>>> + dev_warn(&pdev->dev, "no flash node found\n"); >>>>>>>> + return -ENODEV; >>>>>>>> + } >>>>>>>> + >>>>>>>> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { >>>>>>>> + cell = &rpcif_spi_ctlr; >>>>>>>> + } else if (of_device_is_compatible(flash, "cfi-flash")) { >>>>>>>> + cell = &rpcif_hf_ctlr; >>>>>>>> + } else { >>>>>>>> + dev_warn(&pdev->dev, "unknown flash type\n"); >>>>>>>> + return -ENODEV; >>>>>>>> + } >>>>>>> >>>>>>> Why not let DT choose which device to probe? >>>>>> >>>>>> It's the DT that decides here. How else would you imagine that? >>>>>> It's the same hardware, just the different physical busses that it >>>>>> commands... >>>>> >>>>> DT is not deciding here. This driver is deciding based on the >>>>> information contained in the tables - very different thing. >>>>> >>>>> Why not just let the OF framework probe the correct device i.e. let it >>>>> parse the 2 compatible strings and let it match the correct driver for >>>>> the device? >>>> >>>> The OF framework matches against the RPC-IF node, which is a single >>>> hardware type, hence has a fixed compatible value. >>>> The mode depends on the subnode in DT, which is something the OF >>>> framework doesn't match against, so the driver itself has to check the >>>> subnode's compatible value. >>> >>> I can see how it has been implemented. >>> >>> It is that which I was questioning. >>> >>>> DT describes hardware, not software (Linux subsystem boundary) policy. >>> >>> So is an RPC-IF a real hardware device. Can you share the datasheet? >> >> Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is >> not yet public. > > When will it be public? Ask Renesas. :-) > Do you have access to it? We do... > Maybe someone who does can help with the magic number definitions. Not very likely. :-) >> However, a very similar hardware block is present in the RZ/A2M SoC. >> Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group >> User’s Manual: Hardware", which you can download from >> https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents Also, there's RZ/A1 where a previous version of this hardware seems to be used, see chapter 17 (SPI Multi I/O Bus Controller) of the RZ/A1H/M manual, downloadable from: https://www.renesas.com/us/en/products/microcontrollers-microprocessors/rz/rza/rza1h.html#documents > "The SPI multi I/O bus controller enables the direct connection of > serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory > devices to this LSI chip.> > This module allows the connected serial flash, OctaFlashTM, XccelaTM > flash, or HyperFlashTM memory devices to be accessed by reading the > external address space, or using Manual mode to transmit and receive > data." For contrast, RZ/A1 didn't yet support OctaFlash and HyperFlash, and R-Car gen3 RPC-IF doesn't support Xccella yet... > Looks like a flash device to me. More like a multi-protocol flash controller, with the real flash chip connected to it. MBR, Sergei
On Tue, 18 Jun 2019, Sergei Shtylyov wrote: > On 06/18/2019 12:57 PM, Lee Jones wrote: > > [...] > >>>>>>>> +static int rpcif_probe(struct platform_device *pdev) > >>>>>>>> +{ > >>>>>>>> + struct device_node *flash; > >>>>>>>> + const struct mfd_cell *cell; > >>>>>>>> + struct resource *res; > >>>>>>>> + void __iomem *base; > >>>>>>>> + struct rpcif *rpc; > >>>>>>>> + > >>>>>>>> + flash = of_get_next_child(pdev->dev.of_node, NULL); > >>>>>>>> + if (!flash) { > >>>>>>>> + dev_warn(&pdev->dev, "no flash node found\n"); > >>>>>>>> + return -ENODEV; > >>>>>>>> + } > >>>>>>>> + > >>>>>>>> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { > >>>>>>>> + cell = &rpcif_spi_ctlr; > >>>>>>>> + } else if (of_device_is_compatible(flash, "cfi-flash")) { > >>>>>>>> + cell = &rpcif_hf_ctlr; > >>>>>>>> + } else { > >>>>>>>> + dev_warn(&pdev->dev, "unknown flash type\n"); > >>>>>>>> + return -ENODEV; > >>>>>>>> + } > >>>>>>> > >>>>>>> Why not let DT choose which device to probe? > >>>>>> > >>>>>> It's the DT that decides here. How else would you imagine that? > >>>>>> It's the same hardware, just the different physical busses that it > >>>>>> commands... > >>>>> > >>>>> DT is not deciding here. This driver is deciding based on the > >>>>> information contained in the tables - very different thing. > >>>>> > >>>>> Why not just let the OF framework probe the correct device i.e. let it > >>>>> parse the 2 compatible strings and let it match the correct driver for > >>>>> the device? > >>>> > >>>> The OF framework matches against the RPC-IF node, which is a single > >>>> hardware type, hence has a fixed compatible value. > >>>> The mode depends on the subnode in DT, which is something the OF > >>>> framework doesn't match against, so the driver itself has to check the > >>>> subnode's compatible value. > >>> > >>> I can see how it has been implemented. > >>> > >>> It is that which I was questioning. > >>> > >>>> DT describes hardware, not software (Linux subsystem boundary) policy. > >>> > >>> So is an RPC-IF a real hardware device. Can you share the datasheet? > >> > >> Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is > >> not yet public. > > > > When will it be public? > > Ask Renesas. :-) > > > Do you have access to it? > > We do... > > > Maybe someone who does can help with the magic number definitions. > > Not very likely. :-) > > >> However, a very similar hardware block is present in the RZ/A2M SoC. > >> Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group > >> User’s Manual: Hardware", which you can download from > >> https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents > > Also, there's RZ/A1 where a previous version of this hardware seems to be used, see chapter 17 > (SPI Multi I/O Bus Controller) of the RZ/A1H/M manual, downloadable from: > https://www.renesas.com/us/en/products/microcontrollers-microprocessors/rz/rza/rza1h.html#documents > > > "The SPI multi I/O bus controller enables the direct connection of > > serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory > > devices to this LSI chip.> > > This module allows the connected serial flash, OctaFlashTM, XccelaTM > > flash, or HyperFlashTM memory devices to be accessed by reading the > > external address space, or using Manual mode to transmit and receive > > data." > > For contrast, RZ/A1 didn't yet support OctaFlash and HyperFlash, and R-Car gen3 RPC-IF doesn't > support Xccella yet... > > > Looks like a flash device to me. > > More like a multi-protocol flash controller, with the real flash chip connected > to it. Right, this has been my point from the start. It's a flash controller. Sure, you can access it in different ways, but it's still *just* a flash controller and thus not a true MFD. Surely this whole thing, including the shared portion should live in one of the memory related subsystems? This is not the first device people have tried to shove in MFD, that is really *just* an <X> device, able to be controlled via different protocols. MFD is for registering child devices of chips which offer genuine cross-subsystem functionality. It is not designed for mode selecting, or as a place to shove shared code just because a better location doesn't appear to exist. Also, ramming it into drivers/platform/<vendor> is not correct either, since this is not a platform controller driver either.
On Tue, 18 Jun 2019, Sergei Shtylyov wrote: > Hello! > > On 06/18/2019 02:34 PM, Lee Jones wrote: > > >>>>> So is an RPC-IF a real hardware device. Can you share the datasheet? > >>>> > >>>> Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is > >>>> not yet public. > >>> > >>> When will it be public? > >> > >> Dunno. RZ/G1 documentation became public a few months after the SoC > >> release. > >> > >>> Do you have access to it? > >> > >> Yes I do. > > > > Great. Maybe you can help Sergei with his 'undocumented bits' issue. > > You're an optimist. :-) > I think I have the same gen3 manual v1.50 as Geert. It won't help as the > bits that constitute the magic numbers in the driver are not described (not > even the default values are listed for the most of them). Then how did you get hold of them? > >>>> However, a very similar hardware block is present in the RZ/A2M SoC. > >>>> Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group > >>>> User’s Manual: Hardware", which you can download from > >>>> https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents > >>> > >>> "The SPI multi I/O bus controller enables the direct connection of > >>> serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory > >>> devices to this LSI chip. > >>> > >>> This module allows the connected serial flash, OctaFlashTM, XccelaTM > >>> flash, or HyperFlashTM memory devices to be accessed by reading the > >>> external address space, or using Manual mode to transmit and receive > >>> data." > >>> > >>> Looks like a flash device to me. > >> > >> The external address space is a small window. > > Yeah, it only provides 64 MiB window into the flash chip. > > >>> Can the SPI portion be used to connect generic SPI devices? > >> > >> I'll defer that to the people who worked on the driver... > > Yes. Or at least it should, looking at the driver code... Judging by that response, I'm guessing that this is unused/untested.
Hi Jones, > > > > > Looks like a flash device to me. > > > > More like a multi-protocol flash controller, with the real flash chip connected > > to it. > > Right, this has been my point from the start. > > It's a flash controller. Sure, you can access it in different ways, > but it's still *just* a flash controller and thus not a true MFD. > > Surely this whole thing, including the shared portion should live in > one of the memory related subsystems? > > This is not the first device people have tried to shove in MFD, that > is really *just* an <X> device, able to be controlled via different > protocols. > > MFD is for registering child devices of chips which offer genuine > cross-subsystem functionality. It is not designed for mode selecting, > or as a place to shove shared code just because a better location > doesn't appear to exist. > > Also, ramming it into drivers/platform/<vendor> is not correct either, > since this is not a platform controller driver either. I will patch RPC-IF back to SPI only and rebase onto previous patches as bellow: > "Mark Brown" <broonie@kernel.org> > 2019/02/12 下午 10:22 > > To > > "Mason Yang" <masonccyang@mxic.com.tw>, > > cc > > "Sergei Shtylyov" <sergei.shtylyov@cogentembedded.com>, "Mark Brown" > <broonie@kernel.org>, broonie@kernel.org, marek.vasut@gmail.com, linux- > kernel@vger.kernel.org, linux-spi@vger.kernel.org, bbrezillon@kernel.org, > linux-renesas-soc@vger.kernel.org, "Geert Uytterhoeven" <geert > +renesas@glider.be>, sergei.shtylyov@cogentembedded.com, juliensu@mxic.com.tw, > "Simon Horman" <horms@verge.net.au>, zhengxunli@mxic.com.tw, linux-spi@vger.kernel.org > > Subject > > Applied "spi: Add Renesas R-Car Gen3 RPC-IF SPI controller driver" to the spi tree > > The patch > > spi: Add Renesas R-Car Gen3 RPC-IF SPI controller driver > > has been applied to the spi tree at > > https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git > > All being well this means that it will be integrated into the linux-next > tree (usually sometime in the next 24 hours) and sent to Linus during > the next merge window (or sooner if it is a bug fix), however if > problems are discovered then the patch may be dropped or reverted. thanks & best regards, Mason CONFIDENTIALITY NOTE: This e-mail and any attachments may contain confidential information and/or personal data, which is protected by applicable laws. Please be reminded that duplication, disclosure, distribution, or use of this e-mail (and/or its attachments) or any part thereof is prohibited. If you receive this e-mail in error, please notify us immediately and delete this mail as well as its attachment(s) from your system. In addition, please be informed that collection, processing, and/or use of personal data is prohibited unless expressly permitted by personal data protection laws. Thank you for your attention and cooperation. Macronix International Co., Ltd. ===================================================================== ============================================================================ CONFIDENTIALITY NOTE: This e-mail and any attachments may contain confidential information and/or personal data, which is protected by applicable laws. Please be reminded that duplication, disclosure, distribution, or use of this e-mail (and/or its attachments) or any part thereof is prohibited. If you receive this e-mail in error, please notify us immediately and delete this mail as well as its attachment(s) from your system. In addition, please be informed that collection, processing, and/or use of personal data is prohibited unless expressly permitted by personal data protection laws. Thank you for your attention and cooperation. Macronix International Co., Ltd. =====================================================================
On Wed, 19 Jun 2019, masonccyang@mxic.com.tw wrote: > > > > Looks like a flash device to me. > > > > > > More like a multi-protocol flash controller, with the real flash > chip connected > > > to it. > > > > Right, this has been my point from the start. > > > > It's a flash controller. Sure, you can access it in different ways, > > but it's still *just* a flash controller and thus not a true MFD. > > > > Surely this whole thing, including the shared portion should live in > > one of the memory related subsystems? > > > > This is not the first device people have tried to shove in MFD, that > > is really *just* an <X> device, able to be controlled via different > > protocols. > > > > MFD is for registering child devices of chips which offer genuine > > cross-subsystem functionality. It is not designed for mode selecting, > > or as a place to shove shared code just because a better location > > doesn't appear to exist. > > > > Also, ramming it into drivers/platform/<vendor> is not correct either, > > since this is not a platform controller driver either. > > > I will patch RPC-IF back to SPI only and > rebase onto previous patches as bellow: This sounds more like the easy way out, rather than the right thing to do. Just because this isn't an MFD, doesn't mean it's not suitable for inclusion into the kernel. Take a look at drivers/memory/Kconfig, and see if any of those devices sound familiar.
Hello! On 06/18/2019 12:19 PM, Lee Jones wrote: [...] >>>>>> +static int rpcif_probe(struct platform_device *pdev) >>>>>> +{ >>>>>> + struct device_node *flash; >>>>>> + const struct mfd_cell *cell; >>>>>> + struct resource *res; >>>>>> + void __iomem *base; >>>>>> + struct rpcif *rpc; >>>>>> + >>>>>> + flash = of_get_next_child(pdev->dev.of_node, NULL); >>>>>> + if (!flash) { >>>>>> + dev_warn(&pdev->dev, "no flash node found\n"); >>>>>> + return -ENODEV; >>>>>> + } >>>>>> + >>>>>> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { >>>>>> + cell = &rpcif_spi_ctlr; >>>>>> + } else if (of_device_is_compatible(flash, "cfi-flash")) { >>>>>> + cell = &rpcif_hf_ctlr; >>>>>> + } else { >>>>>> + dev_warn(&pdev->dev, "unknown flash type\n"); >>>>>> + return -ENODEV; >>>>>> + } >>>>> >>>>> Why not let DT choose which device to probe? >>>> >>>> It's the DT that decides here. How else would you imagine that? >>>> It's the same hardware, just the different physical busses that it >>>> commands... >>> >>> DT is not deciding here. This driver is deciding based on the >>> information contained in the tables - very different thing. >>> >>> Why not just let the OF framework probe the correct device i.e. let it >>> parse the 2 compatible strings and let it match the correct driver for >>> the device? How do you imagine that? We typically declare SoC devices in the <soc>.dtsi files so we'd have to override the "compatible" prop in the <board>.dts files? Or we'd just skip that prop in the <soc>.dtsi and specify it only in a <board>.dts. Seems quite ugly... >> The OF framework matches against the RPC-IF node, which is a single >> hardware type, hence has a fixed compatible value. >> The mode depends on the subnode in DT, which is something the OF >> framework doesn't match against, so the driver itself has to check the >> subnode's compatible value. > > I can see how it has been implemented. > > It is that which I was questioning. > >> DT describes hardware, not software (Linux subsystem boundary) policy. > > So is an RPC-IF a real hardware device. Can you share the datasheet? > >> I think you could have two drivers (SPI and MFD) each matching against s/MFD/MTD/? >> the same compatible value, with .probe() functions returning -ENODEV > > No, don't do that. > >> if the subnode doesn't have the appropriate compatible value. >> However, (1) I don't know how well that would play with module >> autoloading based on of_device_id, and (2) that still leaves the question >> where to put the shared code. > > Other than the SPI driver in this set (which I have now looked at), > what else uses the MFD "back-end"? drivers/mtd/hyperbus/. See the (still in-flight) patch set at: http://lists.infradead.org/pipermail/linux-mtd/2019-June/089873.html MBR, Sergei
On 06/19/2019 09:18 AM, Lee Jones wrote: >>>>>>> So is an RPC-IF a real hardware device. Can you share the datasheet? >>>>>> >>>>>> Unfortunately the datasheet for the R-Car Gen3 and RZ/G2 SoCs is >>>>>> not yet public. >>>>> >>>>> When will it be public? >>>> >>>> Dunno. RZ/G1 documentation became public a few months after the SoC >>>> release. >>>> >>>>> Do you have access to it? >>>> >>>> Yes I do. >>> >>> Great. Maybe you can help Sergei with his 'undocumented bits' issue. >> >> You're an optimist. :-) >> I think I have the same gen3 manual v1.50 as Geert. It won't help as the >> bits that constitute the magic numbers in the driver are not described (not >> even the default values are listed for the most of them). > > Then how did you get hold of them? Mason did, not me. And they were copied from the bootloader, IIRC. >>>>>> However, a very similar hardware block is present in the RZ/A2M SoC. >>>>>> Please see Chapter 20 ("SPI Multi I/O Bus Controller") of the "RZ/A2M Group >>>>>> User’s Manual: Hardware", which you can download from >>>>>> https://www.renesas.com/eu/en/products/microcontrollers-microprocessors/rz/rza/rza2m.html#documents >>>>> >>>>> "The SPI multi I/O bus controller enables the direct connection of >>>>> serial flash, OctaFlashTM, XccelaTM flash, or HyperFlashTM memory >>>>> devices to this LSI chip. >>>>> >>>>> This module allows the connected serial flash, OctaFlashTM, XccelaTM >>>>> flash, or HyperFlashTM memory devices to be accessed by reading the >>>>> external address space, or using Manual mode to transmit and receive >>>>> data." >>>>> >>>>> Looks like a flash device to me. >>>> >>>> The external address space is a small window. >> >> Yeah, it only provides 64 MiB window into the flash chip. >> >>>>> Can the SPI portion be used to connect generic SPI devices? >>>> >>>> I'll defer that to the people who worked on the driver... >> >> Yes. Or at least it should, looking at the driver code... > > Judging by that response, I'm guessing that this is unused/untested. I certainly haven't tested it, perhaps Mason did? MBR, Sergei
On 06/19/2019 10:54 AM, Lee Jones wrote: >>>>> Looks like a flash device to me. >>>> >>>> More like a multi-protocol flash controller, with the real flash >>>> chip connected to it. >>> >>> Right, this has been my point from the start. >>> >>> It's a flash controller. Sure, you can access it in different ways, No, the software access will be the same, just the initial controler setup will be somewhat different depending on the flash type used... >>> but it's still *just* a flash controller and thus not a true MFD. Also a SPI controller when a SPI bus is used. >>> Surely this whole thing, including the shared portion should live in >>> one of the memory related subsystems? >>> >>> This is not the first device people have tried to shove in MFD, that >>> is really *just* an <X> device, able to be controlled via different >>> protocols. You somehow still mix the (master) controller and (slave) device, it seems... >>> MFD is for registering child devices of chips which offer genuine >>> cross-subsystem functionality. It is not designed for mode selecting, >>> or as a place to shove shared code just because a better location >>> doesn't appear to exist. OK, fair enough... >>> Also, ramming it into drivers/platform/<vendor> is not correct either, >>> since this is not a platform controller driver either. >> I will patch RPC-IF back to SPI only and >> rebase onto previous patches as bellow: > > This sounds more like the easy way out, rather than the right thing to > do. Just because this isn't an MFD, doesn't mean it's not suitable > for inclusion into the kernel. Take a look at drivers/memory/Kconfig, > and see if any of those devices sound familiar. TI AEMIF sounded familiar, I have some DaVinci/DA8xx background. Trying to wrap my head into (missing?) API in drivers/memory/... MBR, Sergei
On 06/17/2019 12:30 PM, Lee Jones wrote: >>>> Add the MFD driver for Renesas RPC-IF which registers either the SPI or >>>> HyperFLash device depending on the contents of the device tree subnode. >>>> It also provides the absract "back end" device APIs that can be used by >>>> the "front end" SPI/MTD drivers to talk to the real hardware. >>>> >>>> Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. >>>> >>>> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> >> >> [...] >>>> Index: mfd/drivers/mfd/rpc-if.c >>>> =================================================================== >>>> --- /dev/null >>>> +++ mfd/drivers/mfd/rpc-if.c >>>> @@ -0,0 +1,535 @@ [...] >>>> +#define RPCIF_DIRMAP_SIZE 0x4000000 >>> >>> Can you shift this lot out to a header file please. >> >> You mean all register #defne's? Why? I'm not intending to use them outside >> this file. > > Because its 10's of lines of cruft. Thank you! :-) > People won't want to wade through that to get to real functional C > code every time they open up this file. This is how the most drivers are written. > You already have a header file, please use it. Headers are for public things. I've encapsulated the h/w assess into the MFD driver, the client code doesn't have to see all the gory hardware details... IOW, I don't agree to this request. >>>> +void rpcif_enable_rpm(struct rpcif *rpc) >>>> +{ >>>> + pm_runtime_enable(rpc->dev); >>>> +} >>>> +EXPORT_SYMBOL(rpcif_enable_rpm); >>>> + >>>> +void rpcif_disable_rpm(struct rpcif *rpc) >>>> +{ >>>> + pm_runtime_disable(rpc->dev); >>>> +} >>>> +EXPORT_SYMBOL(rpcif_disable_rpm); >>> >>> Where are you exporting these out to? >> >> The "sub-drivers" (SPI/HyperFlash-to-RPC translation drivers). >> >>> Why are you seemingly unnecessarily abstracting out the pm_* API? >> >> With RPM being otherwise controlled by this driver, I thought that should be >> consistent. > > Just use the pm_runtime_*() API directly. This would go against the driver encapsulation as well, I would leave it as is... >>>> +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash) >>>> +{ >>>> + pm_runtime_get_sync(rpc->dev); >>>> + >>>> + /* >>>> + * NOTE: The 0x260 are undocumented bits, but they must be set. >>>> + * RPCIF_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, RPCIF_PHYCNT, >>>> + RPCIF_PHYCNT_CAL | RPCIF_PHYCNT_STRTIM(6) | >>>> + RPCIF_PHYCNT_PHYMEM(hyperflash ? 3 : 0) | 0x260); >>> >>> At least #define it, even it it's not documented. >> >> Do you have an idea how to name such #define? > > How did you find out that they must be set? Mason lifted all this "magic" from the bootloader's driver. > How did you find out the value? > What happens if they are not set? Don't know, maybe Mason does. :-) [...] >>>> +static int wait_msg_xfer_end(struct rpcif *rpc) >>>> +{ >>>> + u32 sts; >>>> + >>>> + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, >>>> + sts & RPCIF_CMNSR_TEND, 0, >>> >>> Aren't you using sts undefined here? >> >> No, the macro reads 'sts' from the register first. > > That's confusing and ugly. > > Please re-write it to the code is clear and easy to read/maintain. OK, I will look into this... [...] >>> Looking at all this code from here ... [...] >>> ... to here. >>> >>> Not sure what all this is, but it looks like it doesn't have anything >> >> This is an abstracted "back end" API for the "front end" MTD/SPI drivers. >> Only this code talks to real RPC-IF hardware. Probably needs some kernel-doc... > > I suggest this needs moving out to a suitable subsystem. > > Possibly MTD. > >>> to do with MFD. I suggest you move it to somewhere more appropriate. >> >> Like where? > > MTD? Well, the new idea is /drivers/memory/, right? >>>> +static const struct mfd_cell rpcif_hf_ctlr = { >>>> + .name = "rpcif-hyperflash", >>>> +}; >>>> + >>>> +static const struct mfd_cell rpcif_spi_ctlr = { >>>> + .name = "rpcif-spi", >>>> +}; >>> >>> This looks like a very tenuous use of the MFD API. >>> >>> I suggest that this isn't actually an MFD at all. >> >> The same hardware supports 2 different physical interfaces, hence >> the drivers have to comply to 2 different driver frameworks... sounded >> like MFD to me. :-) > > Not to me. > > This appears to be some kind of 'mode selector' for an MTD device. > > Lots of drivers have multiple ways to control them - they are not all > MFDs. OK, sounds like drivers/mfd/ are for a single device having multiple distinct subdevices, right? >> [...] >>>> +static int rpcif_probe(struct platform_device *pdev) >>>> +{ >>>> + struct device_node *flash; >>>> + const struct mfd_cell *cell; >>>> + struct resource *res; >>>> + void __iomem *base; >>>> + struct rpcif *rpc; >>>> + >>>> + flash = of_get_next_child(pdev->dev.of_node, NULL); >>>> + if (!flash) { >>>> + dev_warn(&pdev->dev, "no flash node found\n"); >>>> + return -ENODEV; >>>> + } >>>> + >>>> + if (of_device_is_compatible(flash, "jedec,spi-nor")) { >>>> + cell = &rpcif_spi_ctlr; >>>> + } else if (of_device_is_compatible(flash, "cfi-flash")) { >>>> + cell = &rpcif_hf_ctlr; >>>> + } else { >>>> + dev_warn(&pdev->dev, "unknown flash type\n"); >>>> + return -ENODEV; >>>> + } >>> >>> Why not let DT choose which device to probe? >> >> It's the DT that decides here. How else would you imagine that? >> It's the same hardware, just the different physical busses that it >> commands... > > DT is not deciding here. This driver is deciding based on the > information contained in the tables - very different thing. Well, both are done in software. :-) > Why not just let the OF framework probe the correct device i.e. let it > parse the 2 compatible strings and let it match the correct driver for > the device? That doesn't go well with the DT spec, I'm afraid. And much code would be duplicated in case of 2 independent drivers... MBR, Sergei
Hi Sergei, Lee, On Thu, Jun 20, 2019 at 8:46 PM Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> wrote: > On 06/17/2019 12:30 PM, Lee Jones wrote: > >>>> Add the MFD driver for Renesas RPC-IF which registers either the SPI or > >>>> HyperFLash device depending on the contents of the device tree subnode. > >>>> It also provides the absract "back end" device APIs that can be used by > >>>> the "front end" SPI/MTD drivers to talk to the real hardware. > >>>> > >>>> Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. > >>>> > >>>> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> > >> > >> [...] > >>>> Index: mfd/drivers/mfd/rpc-if.c > >>>> =================================================================== > >>>> --- /dev/null > >>>> +++ mfd/drivers/mfd/rpc-if.c > >>>> @@ -0,0 +1,535 @@ > [...] > >>>> +#define RPCIF_DIRMAP_SIZE 0x4000000 > >>> > >>> Can you shift this lot out to a header file please. > >> > >> You mean all register #defne's? Why? I'm not intending to use them outside > >> this file. > > > > Because its 10's of lines of cruft. > > Thank you! :-) > > > People won't want to wade through that to get to real functional C > > code every time they open up this file. > > This is how the most drivers are written. > > > You already have a header file, please use it. > > Headers are for public things. I've encapsulated the h/w assess into > the MFD driver, the client code doesn't have to see all the gory hardware > details... IOW, I don't agree to this request. +1 > >>>> +static int wait_msg_xfer_end(struct rpcif *rpc) > >>>> +{ > >>>> + u32 sts; > >>>> + > >>>> + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, > >>>> + sts & RPCIF_CMNSR_TEND, 0, > >>> > >>> Aren't you using sts undefined here? > >> > >> No, the macro reads 'sts' from the register first. > > > > That's confusing and ugly. > > > > Please re-write it to the code is clear and easy to read/maintain. How to rewrite? This is exactly how the various *_poll_timeout*() helpers are intended to be used. * @val: Unsigned integer variable to read the value into See also include/linux/iopoll.h. Thanks! Gr{oetje,eeting}s, Geert
On Fri, 21 Jun 2019, Geert Uytterhoeven wrote: > Hi Sergei, Lee, > > On Thu, Jun 20, 2019 at 8:46 PM Sergei Shtylyov > <sergei.shtylyov@cogentembedded.com> wrote: > > On 06/17/2019 12:30 PM, Lee Jones wrote: > > >>>> Add the MFD driver for Renesas RPC-IF which registers either the SPI or > > >>>> HyperFLash device depending on the contents of the device tree subnode. > > >>>> It also provides the absract "back end" device APIs that can be used by > > >>>> the "front end" SPI/MTD drivers to talk to the real hardware. > > >>>> > > >>>> Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. > > >>>> > > >>>> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> > > >> > > >> [...] > > >>>> Index: mfd/drivers/mfd/rpc-if.c > > >>>> =================================================================== > > >>>> --- /dev/null > > >>>> +++ mfd/drivers/mfd/rpc-if.c > > >>>> @@ -0,0 +1,535 @@ > > [...] > > >>>> +#define RPCIF_DIRMAP_SIZE 0x4000000 > > >>> > > >>> Can you shift this lot out to a header file please. > > >> > > >> You mean all register #defne's? Why? I'm not intending to use them outside > > >> this file. > > > > > > Because its 10's of lines of cruft. > > > > Thank you! :-) > > > > > People won't want to wade through that to get to real functional C > > > code every time they open up this file. > > > > This is how the most drivers are written. > > > > > You already have a header file, please use it. > > > > Headers are for public things. I've encapsulated the h/w assess into > > the MFD driver, the client code doesn't have to see all the gory hardware > > details... IOW, I don't agree to this request. > > +1 Header files aren't only for sharing. Plenty of source files have their own headers for storing defines which are of little use to the reader. Keeping 125 lines of defines at the top of a source file is pretty ugly and only border-line unsociable. If you had many more, I'd be more insistent. > > >>>> +static int wait_msg_xfer_end(struct rpcif *rpc) > > >>>> +{ > > >>>> + u32 sts; > > >>>> + > > >>>> + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, > > >>>> + sts & RPCIF_CMNSR_TEND, 0, > > >>> > > >>> Aren't you using sts undefined here? > > >> > > >> No, the macro reads 'sts' from the register first. > > > > > > That's confusing and ugly. > > > > > > Please re-write it to the code is clear and easy to read/maintain. > > How to rewrite? > This is exactly how the various *_poll_timeout*() helpers are intended > to be used. > > * @val: Unsigned integer variable to read the value into > > See also include/linux/iopoll.h. Yuck! What a horrible way to write a function. Well if this is how the API is meant to called then I guess it is not you I am taking exception to.
Hello, > Re: [PATCH RFC 2/2] mfd: add Renesas RPC-IF driver > > On 06/19/2019 10:54 AM, Lee Jones wrote: > > >>>>> Looks like a flash device to me. > >>>> > >>>> More like a multi-protocol flash controller, with the real flash > >>>> chip connected to it. > >>> > >>> Right, this has been my point from the start. > >>> > >>> It's a flash controller. Sure, you can access it in different ways, > > No, the software access will be the same, just the initial controler > setup will be somewhat different depending on the flash type used... > > >>> but it's still *just* a flash controller and thus not a true MFD. > > Also a SPI controller when a SPI bus is used. Cypress has announced the inclusion of Cypress’ high-bandwidth HyperBus™ 8-bit serial memory interface into the new eXpanded SPI (xSPI) electrical interface standard from the JEDEC Solid State Technology Association for detail, please goes to https://www.cypress.com/news/cypress-hyperbus-memory-interface-instant-applications-incorporated-jedec-xspi-electrical As mentioned before that HyperFlash is a kind of standard NOR Flash with high-speed SPI interface. thanks & best regards, Mason CONFIDENTIALITY NOTE: This e-mail and any attachments may contain confidential information and/or personal data, which is protected by applicable laws. Please be reminded that duplication, disclosure, distribution, or use of this e-mail (and/or its attachments) or any part thereof is prohibited. If you receive this e-mail in error, please notify us immediately and delete this mail as well as its attachment(s) from your system. In addition, please be informed that collection, processing, and/or use of personal data is prohibited unless expressly permitted by personal data protection laws. Thank you for your attention and cooperation. Macronix International Co., Ltd. ===================================================================== ============================================================================ CONFIDENTIALITY NOTE: This e-mail and any attachments may contain confidential information and/or personal data, which is protected by applicable laws. Please be reminded that duplication, disclosure, distribution, or use of this e-mail (and/or its attachments) or any part thereof is prohibited. If you receive this e-mail in error, please notify us immediately and delete this mail as well as its attachment(s) from your system. In addition, please be informed that collection, processing, and/or use of personal data is prohibited unless expressly permitted by personal data protection laws. Thank you for your attention and cooperation. Macronix International Co., Ltd. =====================================================================
Index: mfd/drivers/mfd/Kconfig =================================================================== --- mfd.orig/drivers/mfd/Kconfig +++ mfd/drivers/mfd/Kconfig @@ -1002,6 +1002,16 @@ config MFD_RDC321X southbridge which provides access to GPIOs and Watchdog using the southbridge PCI device configuration space. +config MFD_RPCIF + tristate "Renesas RPC-IF driver" + select MFD_CORE + select REGMAP_MMIO + depends on ARCH_RENESAS + help + This supports Renesas R-Car Gen3 RPC-IF which provides either SPI + host or HyperFlash. You'll have to select individual components + under the corresponding menu. + config MFD_RT5033 tristate "Richtek RT5033 Power Management IC" depends on I2C Index: mfd/drivers/mfd/Makefile =================================================================== --- mfd.orig/drivers/mfd/Makefile +++ mfd/drivers/mfd/Makefile @@ -184,6 +184,7 @@ obj-$(CONFIG_MFD_INTEL_QUARK_I2C_GPIO) + obj-$(CONFIG_LPC_SCH) += lpc_sch.o obj-$(CONFIG_LPC_ICH) += lpc_ich.o obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o +obj-$(CONFIG_MFD_RPCIF) += rpc-if.o obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o Index: mfd/drivers/mfd/rpc-if.c =================================================================== --- /dev/null +++ mfd/drivers/mfd/rpc-if.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RPC-IF MFD driver + * + * Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. + * Copyright (C) 2019 Macronix International Co., Ltd. + * Copyright (C) 2019 Cogent Embedded, Inc. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rpc-if.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <asm/unaligned.h> + +#define RPCIF_CMNCR 0x0000 // R/W +#define RPCIF_CMNCR_MD BIT(31) +#define RPCIF_CMNCR_SFDE BIT(24) // undocumented bit but must be set +#define RPCIF_CMNCR_MOIIO3(val) (((val) & 0x3) << 22) +#define RPCIF_CMNCR_MOIIO2(val) (((val) & 0x3) << 20) +#define RPCIF_CMNCR_MOIIO1(val) (((val) & 0x3) << 18) +#define RPCIF_CMNCR_MOIIO0(val) (((val) & 0x3) << 16) +#define RPCIF_CMNCR_MOIIO_HIZ (RPCIF_CMNCR_MOIIO0(3) | \ + RPCIF_CMNCR_MOIIO1(3) | \ + RPCIF_CMNCR_MOIIO2(3) | RPCIF_CMNCR_MOIIO3(3)) +#define RPCIF_CMNCR_IO3FV(val) (((val) & 0x3) << 14) // undocumented +#define RPCIF_CMNCR_IO2FV(val) (((val) & 0x3) << 12) // undocumented +#define RPCIF_CMNCR_IO0FV(val) (((val) & 0x3) << 8) +#define RPCIF_CMNCR_IOFV_HIZ (RPCIF_CMNCR_IO0FV(3) | RPCIF_CMNCR_IO2FV(3) | \ + RPCIF_CMNCR_IO3FV(3)) +#define RPCIF_CMNCR_BSZ(val) (((val) & 0x3) << 0) + +#define RPCIF_SSLDR 0x0004 // R/W +#define RPCIF_SSLDR_SPNDL(d) (((d) & 0x7) << 16) +#define RPCIF_SSLDR_SLNDL(d) (((d) & 0x7) << 8) +#define RPCIF_SSLDR_SCKDL(d) (((d) & 0x7) << 0) + +#define RPCIF_DRCR 0x000C // R/W +#define RPCIF_DRCR_SSLN BIT(24) +#define RPCIF_DRCR_RBURST(v) ((((v) - 1) & 0x1F) << 16) +#define RPCIF_DRCR_RCF BIT(9) +#define RPCIF_DRCR_RBE BIT(8) +#define RPCIF_DRCR_SSLE BIT(0) + +#define RPCIF_DRCMR 0x0010 // R/W +#define RPCIF_DRCMR_CMD(c) (((c) & 0xFF) << 16) +#define RPCIF_DRCMR_OCMD(c) (((c) & 0xFF) << 0) + +#define RPCIF_DREAR 0x0014 // R/W +#define RPCIF_DREAR_EAV(c) (((c) & 0xf) << 16) +#define RPCIF_DREAR_EAC(c) (((c) & 0x7) << 0) + +#define RPCIF_DROPR 0x0018 // R/W + +#define RPCIF_DRENR 0x001C // R/W +#define RPCIF_DRENR_CDB(o) (u32)((((o) & 0x3) << 30)) +#define RPCIF_DRENR_OCDB(o) (((o) & 0x3) << 28) +#define RPCIF_DRENR_ADB(o) (((o) & 0x3) << 24) +#define RPCIF_DRENR_OPDB(o) (((o) & 0x3) << 20) +#define RPCIF_DRENR_DRDB(o) (((o) & 0x3) << 16) +#define RPCIF_DRENR_DME BIT(15) +#define RPCIF_DRENR_CDE BIT(14) +#define RPCIF_DRENR_OCDE BIT(12) +#define RPCIF_DRENR_ADE(v) (((v) & 0xF) << 8) +#define RPCIF_DRENR_OPDE(v) (((v) & 0xF) << 4) + +#define RPCIF_SMCR 0x0020 // R/W +#define RPCIF_SMCR_SSLKP BIT(8) +#define RPCIF_SMCR_SPIRE BIT(2) +#define RPCIF_SMCR_SPIWE BIT(1) +#define RPCIF_SMCR_SPIE BIT(0) + +#define RPCIF_SMCMR 0x0024 // R/W +#define RPCIF_SMCMR_CMD(c) (((c) & 0xFF) << 16) +#define RPCIF_SMCMR_OCMD(c) (((c) & 0xFF) << 0) + +#define RPCIF_SMADR 0x0028 // R/W +#define RPCIF_SMOPR 0x002C // R/W +#define RPCIF_SMOPR_OPD3(o) (((o) & 0xFF) << 24) +#define RPCIF_SMOPR_OPD2(o) (((o) & 0xFF) << 16) +#define RPCIF_SMOPR_OPD1(o) (((o) & 0xFF) << 8) +#define RPCIF_SMOPR_OPD0(o) (((o) & 0xFF) << 0) + +#define RPCIF_SMENR 0x0030 // R/W +#define RPCIF_SMENR_CDB(o) (((o) & 0x3) << 30) +#define RPCIF_SMENR_OCDB(o) (((o) & 0x3) << 28) +#define RPCIF_SMENR_ADB(o) (((o) & 0x3) << 24) +#define RPCIF_SMENR_OPDB(o) (((o) & 0x3) << 20) +#define RPCIF_SMENR_SPIDB(o) (((o) & 0x3) << 16) +#define RPCIF_SMENR_DME BIT(15) +#define RPCIF_SMENR_CDE BIT(14) +#define RPCIF_SMENR_OCDE BIT(12) +#define RPCIF_SMENR_ADE(v) (((v) & 0xF) << 8) +#define RPCIF_SMENR_OPDE(v) (((v) & 0xF) << 4) +#define RPCIF_SMENR_SPIDE(v) (((v) & 0xF) << 0) + +#define RPCIF_SMRDR0 0x0038 // R +#define RPCIF_SMRDR1 0x003C // R +#define RPCIF_SMWDR0 0x0040 // W +#define RPCIF_SMWDR1 0x0044 // W + +#define RPCIF_CMNSR 0x0048 // R +#define RPCIF_CMNSR_SSLF BIT(1) +#define RPCIF_CMNSR_TEND BIT(0) + +#define RPCIF_DRDMCR 0x0058 // R/W +#define RPCIF_DMDMCR_DMCYC(v) ((((v) - 1) & 0x1F) << 0) + +#define RPCIF_DRDRENR 0x005C // R/W +#define RPCIF_DRDRENR_HYPE(v) (((v) & 0x7) << 12) +#define RPCIF_DRDRENR_ADDRE BIT(8) +#define RPCIF_DRDRENR_OPDRE BIT(4) +#define RPCIF_DRDRENR_DRDRE BIT(0) + +#define RPCIF_SMDMCR 0x0060 // R/W +#define RPCIF_SMDMCR_DMCYC(v) ((((v) - 1) & 0x1F) << 0) + +#define RPCIF_SMDRENR 0x0064 // R/W +#define RPCIF_SMDRENR_HYPE(v) (((v) & 0x7) << 12) +#define RPCIF_SMDRENR_ADDRE BIT(8) +#define RPCIF_SMDRENR_OPDRE BIT(4) +#define RPCIF_SMDRENR_SPIDRE BIT(0) + +#define RPCIF_PHYCNT 0x007C // R/W +#define RPCIF_PHYCNT_CAL BIT(31) +#define RPCIF_PHYCNT_OCTA_AA BIT(22) +#define RPCIF_PHYCNT_OCTA_SA BIT(23) +#define RPCIF_PHYCNT_EXDS BIT(21) +#define RPCIF_PHYCNT_OCT BIT(20) +#define RPCIF_PHYCNT_STRTIM(v) (((v) & 0x7) << 15) +#define RPCIF_PHYCNT_WBUF2 BIT(4) +#define RPCIF_PHYCNT_WBUF BIT(2) +#define RPCIF_PHYCNT_PHYMEM(v) (((v) & 0x3) << 0) + +#define RPCIF_PHYOFFSET1 0x0080 // R/W +#define RPCIF_PHYOFFSET1_DDRTMG(v) (((v) & 0x3) << 28) +#define RPCIF_PHYOFFSET2 0x0084 // R/W +#define RPCIF_PHYOFFSET2_OCTTMG(v) (((v) & 0x7) << 8) + +#define RPCIF_DIRMAP_SIZE 0x4000000 + +void rpcif_enable_rpm(struct rpcif *rpc) +{ + pm_runtime_enable(rpc->dev); +} +EXPORT_SYMBOL(rpcif_enable_rpm); + +void rpcif_disable_rpm(struct rpcif *rpc) +{ + pm_runtime_disable(rpc->dev); +} +EXPORT_SYMBOL(rpcif_disable_rpm); + +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash) +{ + pm_runtime_get_sync(rpc->dev); + + /* + * NOTE: The 0x260 are undocumented bits, but they must be set. + * RPCIF_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, RPCIF_PHYCNT, + RPCIF_PHYCNT_CAL | RPCIF_PHYCNT_STRTIM(6) | + RPCIF_PHYCNT_PHYMEM(hyperflash ? 3 : 0) | 0x260); + + /* + * NOTE: The 0x1511144 are undocumented bits, but they must be set + * for RPCIF_PHYOFFSET1. + * The 0x31 are undocumented bits, but they must be set + * for RPCIF_PHYOFFSET2. + */ + regmap_write(rpc->regmap, RPCIF_PHYOFFSET1, + RPCIF_PHYOFFSET1_DDRTMG(3) | 0x1511144); + regmap_write(rpc->regmap, RPCIF_PHYOFFSET2, 0x31 | + RPCIF_PHYOFFSET2_OCTTMG(4)); + + regmap_write(rpc->regmap, RPCIF_SSLDR, RPCIF_SSLDR_SPNDL(7) | + RPCIF_SSLDR_SLNDL(7) | RPCIF_SSLDR_SCKDL(7)); + regmap_write(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD | + RPCIF_CMNCR_SFDE | RPCIF_CMNCR_MOIIO_HIZ | + RPCIF_CMNCR_IOFV_HIZ | + RPCIF_CMNCR_BSZ(hyperflash ? 1 : 0)); + + pm_runtime_put(rpc->dev); +} +EXPORT_SYMBOL(rpcif_hw_init); + +static int wait_msg_xfer_end(struct rpcif *rpc) +{ + u32 sts; + + return regmap_read_poll_timeout(rpc->regmap, RPCIF_CMNSR, sts, + sts & RPCIF_CMNSR_TEND, 0, + USEC_PER_SEC); +} + +static u8 rpcif_bits_set(u32 nbytes) +{ + nbytes = clamp(nbytes, 1U, 4U); + + return GENMASK(3, 4 - nbytes); +} + +void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, + size_t *len) +{ + rpc->enable = 0; + rpc->command = 0; + rpc->xferlen = 0; + rpc->address = 0; + rpc->ddr = 0; + + if (op->cmd.buswidth) { + rpc->enable |= RPCIF_SMENR_CDE | + RPCIF_SMENR_CDB(ilog2(op->cmd.buswidth)); + rpc->command |= RPCIF_SMCMR_CMD(op->cmd.opcode); + if (op->cmd.ddr) + rpc->ddr |= RPCIF_SMDRENR_HYPE(0x5); + } + if (op->ocmd.buswidth) { + rpc->enable |= RPCIF_SMENR_OCDE | + RPCIF_SMENR_OCDB(ilog2(op->ocmd.buswidth)); + rpc->command |= RPCIF_SMCMR_OCMD(op->ocmd.opcode); + } + + if (op->addr.buswidth) { + rpc->enable |= RPCIF_SMENR_ADB(ilog2(op->addr.buswidth)); + if (op->addr.nbytes == 4) + rpc->enable |= RPCIF_SMENR_ADE(0xf); + else + rpc->enable |= RPCIF_SMENR_ADE(0x7); + if (op->addr.ddr) + rpc->ddr |= RPCIF_SMDRENR_ADDRE; + + if (offs && len) + rpc->address = *offs; + else + rpc->address = op->addr.val; + } + + if (op->dummy.buswidth) { + rpc->enable |= RPCIF_SMENR_DME; + rpc->dummy = RPCIF_SMDMCR_DMCYC(op->dummy.nbytes * 8 / + op->dummy.buswidth); + } + + if (op->option.buswidth) { + rpc->enable |= RPCIF_SMENR_OPDE( + rpcif_bits_set(op->option.nbytes)) | + RPCIF_SMENR_OPDB(ilog2(op->option.buswidth)); + if (op->option.ddr) + rpc->ddr |= RPCIF_SMDRENR_OPDRE; + rpc->option = op->option.val; + } + + if (op->data.buswidth || (offs && len)) { + switch (op->data.dir) { + case RPCIF_DATA_IN: + rpc->smcr = RPCIF_SMCR_SPIRE; + break; + case RPCIF_DATA_OUT: + rpc->smcr = RPCIF_SMCR_SPIWE; + break; + default: + break; + } + if (op->data.ddr) + rpc->ddr |= RPCIF_SMDRENR_SPIDRE; + + if (offs && len) { + rpc->enable |= RPCIF_SMENR_SPIDE(rpcif_bits_set(*len)) | + RPCIF_SMENR_SPIDB( + ilog2(op->data.buswidth)); + rpc->xferlen = *len; + } else { + rpc->enable |= + RPCIF_SMENR_SPIDE( + rpcif_bits_set(op->data.nbytes)) | + RPCIF_SMENR_SPIDB(ilog2(op->data.buswidth)); + rpc->xferlen = op->data.nbytes; + } + } +} +EXPORT_SYMBOL(rpcif_prepare); + +int rpcif_io_xfer(struct rpcif *rpc, const void *tx_buf, void *rx_buf) +{ + u32 smenr, smcr, data, pos = 0; + int ret = 0; + + pm_runtime_get_sync(rpc->dev); + + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, + RPCIF_CMNCR_MD); + regmap_write(rpc->regmap, RPCIF_SMCMR, rpc->command); + regmap_write(rpc->regmap, RPCIF_SMDMCR, rpc->dummy); + regmap_write(rpc->regmap, RPCIF_SMADR, rpc->address); + regmap_write(rpc->regmap, RPCIF_SMDRENR, rpc->ddr); + smenr = rpc->enable; + + if (tx_buf) { + while (pos < rpc->xferlen) { + u32 nbytes = rpc->xferlen - pos; + + regmap_write(rpc->regmap, RPCIF_SMWDR0, + get_unaligned((u32 *)(tx_buf + pos))); + + smcr = rpc->smcr | RPCIF_SMCR_SPIE; + + if (nbytes > 4) { + nbytes = 4; + smcr |= RPCIF_SMCR_SSLKP; + } + + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); + regmap_write(rpc->regmap, RPCIF_SMCR, smcr); + ret = wait_msg_xfer_end(rpc); + if (ret) + goto err_out; + + pos += nbytes; + smenr = rpc->enable & + ~RPCIF_SMENR_CDE & ~RPCIF_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 & RPCIF_SMENR_ADE(0xf)) && rpc->dirmap) { + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, + RPCIF_CMNCR_MD, 0); + regmap_write(rpc->regmap, RPCIF_DRCR, + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); + regmap_write(rpc->regmap, RPCIF_DREAR, + RPCIF_DREAR_EAC(1)); + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); + regmap_write(rpc->regmap, RPCIF_DROPR, 0); + regmap_write(rpc->regmap, RPCIF_DRENR, + smenr & ~RPCIF_SMENR_SPIDE(0xf)); + memcpy_fromio(rx_buf, rpc->dirmap, rpc->xferlen); + regmap_write(rpc->regmap, RPCIF_DRCR, RPCIF_DRCR_RCF); + } else { + while (pos < rpc->xferlen) { + u32 nbytes = rpc->xferlen - pos; + + if (nbytes > 4) + nbytes = 4; + + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); + regmap_write(rpc->regmap, RPCIF_SMCR, + rpc->smcr | RPCIF_SMCR_SPIE); + ret = wait_msg_xfer_end(rpc); + if (ret) + goto err_out; + + regmap_read(rpc->regmap, RPCIF_SMRDR0, &data); + memcpy(rx_buf + pos, &data, nbytes); + pos += nbytes; + + regmap_write(rpc->regmap, RPCIF_SMADR, + rpc->address + pos); + } + } + } else { + regmap_write(rpc->regmap, RPCIF_SMENR, rpc->enable); + regmap_write(rpc->regmap, RPCIF_SMCR, + rpc->smcr | RPCIF_SMCR_SPIE); + ret = wait_msg_xfer_end(rpc); + if (ret) + goto err_out; + } + +exit: + pm_runtime_put(rpc->dev); + return ret; + +err_out: + ret = reset_control_reset(rpc->rstc); + goto exit; +} +EXPORT_SYMBOL(rpcif_io_xfer); + +ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf) +{ + loff_t from = offs & (RPCIF_DIRMAP_SIZE - 1); + size_t size = RPCIF_DIRMAP_SIZE - from; + int rc; + + if (len > size) + len = size; + + rc = pm_runtime_get_sync(rpc->dev); + + regmap_update_bits(rpc->regmap, RPCIF_CMNCR, RPCIF_CMNCR_MD, 0); + regmap_write(rpc->regmap, RPCIF_DRCR, + RPCIF_DRCR_RBURST(32) | RPCIF_DRCR_RBE); + + regmap_write(rpc->regmap, RPCIF_DRCMR, rpc->command); + regmap_write(rpc->regmap, RPCIF_DREAR, + RPCIF_DREAR_EAV(offs >> 25) | RPCIF_DREAR_EAC(1)); + regmap_write(rpc->regmap, RPCIF_DROPR, rpc->option); + regmap_write(rpc->regmap, RPCIF_DRENR, + rpc->enable & ~RPCIF_SMENR_SPIDE(0xF)); + regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); + regmap_write(rpc->regmap, RPCIF_DRDRENR, rpc->ddr); + + memcpy_fromio(buf, rpc->dirmap + from, len); + + pm_runtime_put(rpc->dev); + + return len; +} +EXPORT_SYMBOL(rpcif_dirmap_read); + +static const struct mfd_cell rpcif_hf_ctlr = { + .name = "rpcif-hyperflash", +}; + +static const struct mfd_cell rpcif_spi_ctlr = { + .name = "rpcif-spi", +}; + +static const struct regmap_range rpcif_volatile_ranges[] = { + regmap_reg_range(RPCIF_SMRDR0, RPCIF_SMRDR1), + regmap_reg_range(RPCIF_SMWDR0, RPCIF_SMWDR1), + regmap_reg_range(RPCIF_CMNSR, RPCIF_CMNSR), +}; + +static const struct regmap_access_table rpcif_volatile_table = { + .yes_ranges = rpcif_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(rpcif_volatile_ranges), +}; + +static const struct regmap_config rpcif_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, + .max_register = RPCIF_PHYOFFSET2, + .volatile_table = &rpcif_volatile_table, +}; + +static int rpcif_probe(struct platform_device *pdev) +{ + struct device_node *flash; + const struct mfd_cell *cell; + struct resource *res; + void __iomem *base; + struct rpcif *rpc; + + flash = of_get_next_child(pdev->dev.of_node, NULL); + if (!flash) { + dev_warn(&pdev->dev, "no flash node found\n"); + return -ENODEV; + } + + if (of_device_is_compatible(flash, "jedec,spi-nor")) { + cell = &rpcif_spi_ctlr; + } else if (of_device_is_compatible(flash, "cfi-flash")) { + cell = &rpcif_hf_ctlr; + } else { + dev_warn(&pdev->dev, "unknown flash type\n"); + return -ENODEV; + } + + rpc = devm_kzalloc(&pdev->dev, sizeof(*rpc), GFP_KERNEL); + if (!rpc) + return -ENOMEM; + + rpc->dev = &pdev->dev; + + rpc->clk_rpcif = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(rpc->clk_rpcif)) + return PTR_ERR(rpc->clk_rpcif); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + rpc->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &rpcif_regmap_config); + if (IS_ERR(rpc->regmap)) { + dev_err(&pdev->dev, + "failed to init regmap for rpcif, 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; + + rpc->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(rpc->rstc)) + return PTR_ERR(rpc->rstc); + + platform_set_drvdata(pdev, rpc); + + return devm_mfd_add_devices(&pdev->dev, -1, cell, 1, NULL, 0, NULL); +} + +static const struct of_device_id rpcif_of_match[] = { + { .compatible = "renesas,rcar-gen3-rpcif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpcif_of_match); + +static struct platform_driver rpcif_driver = { + .probe = rpcif_probe, + .driver = { + .name = "rpcif", + .of_match_table = rpcif_of_match, + }, +}; +module_platform_driver(rpcif_driver); + +MODULE_DESCRIPTION("Renesas RPC-IF MFD driver"); +MODULE_LICENSE("GPL v2"); Index: mfd/include/linux/mfd/rpc-if.h =================================================================== --- /dev/null +++ mfd/include/linux/mfd/rpc-if.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. + * Copyright (C) 2019 Macronix International Co., Ltd. + * + * R-Car Gen3 RPC-IF MFD driver + * + * Author: + * Mason Yang <masonccyang@mxic.com.tw> + */ + +#ifndef __MFD_RPC_IF_H +#define __MFD_RPC_IF_H + +#include <linux/types.h> + +enum rpcif_data_dir { + RPCIF_NO_DATA, + RPCIF_DATA_IN, + RPCIF_DATA_OUT, +}; + +struct rpcif_op { + struct { + u8 buswidth; + u8 opcode; + bool ddr; + } cmd, ocmd; + + struct { + u8 nbytes; + u8 buswidth; + bool ddr; + u64 val; + } addr; + + struct { + u8 nbytes; + u8 buswidth; + } dummy; + + struct { + u8 nbytes; + u8 buswidth; + bool ddr; + u32 val; + } option; + + struct { + u8 buswidth; + enum rpcif_data_dir dir; + unsigned int nbytes; + bool ddr; + union { + void *in; + const void *out; + } buf; + } data; +}; + +struct rpcif { + struct device *dev; + struct clk *clk_rpcif; + void __iomem *dirmap; + struct regmap *regmap; + struct reset_control *rstc; + u32 enable; + u32 command; + u32 address; + u32 dummy; + u32 option; + u32 ddr; + u32 smcr; + u32 xferlen; +}; + +void rpcif_enable_rpm(struct rpcif *rpc); +void rpcif_disable_rpm(struct rpcif *rpc); +void rpcif_hw_init(struct rpcif *rpc, bool hyperflash); +void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, + size_t *len); +int rpcif_io_xfer(struct rpcif *rpc, const void *tx_buf, void *rx_buf); +ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf); + +#endif // __MFD_RPC_IF_H
Add the MFD driver for Renesas RPC-IF which registers either the SPI or HyperFLash device depending on the contents of the device tree subnode. It also provides the absract "back end" device APIs that can be used by the "front end" SPI/MTD drivers to talk to the real hardware. Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> --- drivers/mfd/Kconfig | 10 drivers/mfd/Makefile | 1 drivers/mfd/rpc-if.c | 535 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/rpc-if.h | 85 +++++++ 4 files changed, 631 insertions(+)