Re: [PATCH RFC 2/2] mfd: add Renesas RPC-IF driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxx>.
> >>
> >> Signed-off-by: Sergei Shtylyov <sergei.shtylyov@xxxxxxxxxxxxxxxxxx>
> 
> [...]
> >> 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?

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog



[Index of Archives]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux