[PATCH v3 2/2] mtd: spi-nor: add rockchip serial flash controller driver

Shawn Lin shawn.lin at rock-chips.com
Mon Dec 5 18:56:26 PST 2016


On 2016/12/5 11:40, Marek Vasut wrote:
> On 12/05/2016 03:56 AM, Shawn Lin wrote:
>> Add rockchip serial flash controller driver
>>
>> Signed-off-by: Shawn Lin <shawn.lin at rock-chips.com>
>>
>
> Looks good, a few nits below.
>
> [...]
>
>> +static int get_if_type(struct rockchip_sfc *sfc, enum read_mode flash_read)
>> +{
>> +	enum rockchip_sfc_iftype if_type;
>
> Wouldn't it be shorter if you used if-return below ?
> Example
>
> if (flash_read == SPI_NOR_QUAD)
> 	return IF_TYPE_QUAD;
>
> if (flash_read == SPI_NOR_DUAL)
> 	return IF_TYPE_DUAL;
> ...

ok, will improve.

>
> dev_err(sfc->dev, "unsupported SPI read mode\n");
> return -EINVAL;
>
>> +	switch (flash_read) {
>> +	case SPI_NOR_DUAL:
>> +		if_type = IF_TYPE_DUAL;
>> +		break;
>> +	case SPI_NOR_QUAD:
>> +		if_type = IF_TYPE_QUAD;
>> +		break;
>> +	case SPI_NOR_NORMAL:
>> +	case SPI_NOR_FAST:
>> +		if_type = IF_TYPE_STD;
>> +		break;
>> +	default:
>> +		dev_err(sfc->dev, "unsupported SPI read mode\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return if_type;
>> +}
>
> [...]
>
>> +static inline void rockchip_sfc_setup_transfer(struct spi_nor *nor,
>> +					       loff_t from_to,
>> +					       size_t len, u8 op_type)
>> +{
>> +	struct rockchip_sfc_chip_priv *priv = nor->priv;
>> +	struct rockchip_sfc *sfc = priv->sfc;
>> +	u32 reg;
>> +	u8 if_type = 0;
>> +
>> +	if_type = get_if_type(sfc, nor->flash_read);
>> +	writel_relaxed((if_type << SFC_CTRL_DATA_BITS_SHIFT) |
>> +		       (if_type << SFC_CTRL_ADDR_BITS_SHIFT) |
>> +		       (if_type << SFC_CTRL_CMD_BITS_SHIFT) |
>
> Hm, looking at this, does the controller only support n-n-n mode (1-1-1,
> 2-2-2, 4-4-4) ? Or why don't you allow 1-1-n/1-n-n/2-n-n ?

No, it also could support 1-1-n, etc.
By looking at the cadence-quadspi.c,  it only allows
CQSPI_INST_TYPE_SINGLE for f_pdata->addr_width and f_pdata->inst_width,
so finally it only supports 1-1-1, 1-1-2, 1-1-4?

> I would like to hear some input from Cyrille on this one.
>
>> +		       (sfc->negative_edge ? SFC_CTRL_PHASE_SEL_NEGETIVE : 0),
>> +		       sfc->regbase + SFC_CTRL);
>> +
>> +	if (op_type == SFC_CMD_DIR_WR)
>> +		reg = nor->program_opcode << SFC_CMD_IDX_SHIFT;
>> +	else
>> +		reg = nor->read_opcode << SFC_CMD_IDX_SHIFT;
>> +
>> +	reg |= op_type << SFC_CMD_DIR_SHIFT;
>> +	reg |= (nor->addr_width == 4) ?
>> +		SFC_CMD_ADDR_32BITS : SFC_CMD_ADDR_24BITS;
>> +
>> +	reg |= priv->cs << SFC_CMD_CS_SHIFT;
>> +	reg |= len << SFC_CMD_TRAN_BYTES_SHIFT;
>> +
>> +	if (op_type == SFC_CMD_DIR_RD)
>> +		reg |= SFC_CMD_DUMMY(nor->read_dummy);
>> +
>> +	/* Should minus one as 0x0 means 1 bit flash address */
>> +	writel_relaxed(nor->addr_width * 8 - 1, sfc->regbase + SFC_ABIT);
>> +	writel_relaxed(reg, sfc->regbase + SFC_CMD);
>> +	writel_relaxed(from_to, sfc->regbase + SFC_ADDR);
>> +}
>
>
> [...]
>
>> +static int rockchip_sfc_dma_transfer(struct spi_nor *nor, loff_t from_to,
>> +				     size_t len, u_char *buf, u8 op_type)
>> +{
>> +	struct rockchip_sfc_chip_priv *priv = nor->priv;
>> +	struct rockchip_sfc *sfc = priv->sfc;
>> +	size_t offset;
>> +	int ret;
>> +	dma_addr_t dma_addr = 0;
>
> Nit, you can precalculate the DMA_TO/FROM_DEVICE and store it to
> variable here, ie.
>
> dma_dir = (op_type == SFC_CMD_DIR_RD) ? DMA_FROM_DEVICE : DMA_TO_DEVICE.
>
>> +	for (offset = 0; offset < len; offset += SFC_DMA_MAX_LEN) {
>> +		size_t trans = min_t(size_t, SFC_DMA_MAX_LEN, len - offset);
>> +
>> +		if (SFC_CMD_DIR_RD)
>
> if (op_type == is missing, but you can drop this, see above.

okay

>
>> +			dma_addr = dma_map_single(NULL, (void *)buf,
>> +						  trans, DMA_FROM_DEVICE);
>> +		else
>> +			dma_addr = dma_map_single(NULL, (void *)buf,
>> +						  trans, DMA_TO_DEVICE);
>
> You can use dma_dir here ^ and drop the condition.

sure

>
>> +		if (dma_mapping_error(sfc->dev, dma_addr)) {
>> +			/*
>> +			 * If we use pre-allocated dma_buffer, we need to
>> +			 * do a copy here.
>> +			 */
>> +			if (op_type == SFC_CMD_DIR_WR)
>> +				memcpy(sfc->buffer, buf + offset, trans);
>> +
>> +			dma_addr = 0;
>> +		}
>> +
>> +		if (op_type == SFC_CMD_DIR_WR)
>> +			/*
>> +			 * Flush the write data from write_buf to dma_addr
>> +			 * if using dynamic allocated dma buffer before dma
>> +			 * moves data from dma_addr to fifo.
>> +			 */
>> +			dma_sync_single_for_device(sfc->dev, dma_addr,
>> +						   trans, DMA_TO_DEVICE);
>> +
>> +
>> +		/* If failing to map dma, use pre-allocated area instead */
>> +		ret = rockchip_sfc_do_dma_transfer(nor, from_to + offset,
>> +						dma_addr ? dma_addr :
>> +						sfc->dma_buffer,
>> +						trans, op_type);
>> +
>> +		if (dma_addr) {
>> +			/*
>> +			 * Invalidate the read data from dma_addr if using
>> +			 * dynamic allocated dma buffer after dma moves data
>> +			 * from fifo to dma_addr.
>> +			 */
>> +			if (op_type == SFC_CMD_DIR_RD) {
>> +				dma_sync_single_for_cpu(sfc->dev, dma_addr,
>> +							trans, DMA_FROM_DEVICE);
>> +				dma_unmap_single(NULL, dma_addr,
>> +						 trans, DMA_FROM_DEVICE);
>> +			} else {
>> +				dma_unmap_single(NULL, dma_addr,
>> +						 trans, DMA_TO_DEVICE);
>
> Here as well and it'd be reduced to
>
> if (...)
>   dma_sync_single...()
> dma_unmap( ... , dma_dir);

sure.

>
>> +			}
>> +		}
>> +
>> +		if (ret) {
>> +			dev_warn(nor->dev, "DMA read timeout\n");
>> +			return ret;
>> +		}
>> +		/*
>> +		 * If we use pre-allocated dma_buffer for read, we need to
>> +		 * do a copy here.
>> +		 */
>> +		if (!dma_addr && (op_type == SFC_CMD_DIR_RD))
>> +			memcpy(buf + offset, sfc->buffer, trans);
>> +	}
>> +
>> +	return len;
>> +}
>> +
>> +static ssize_t rockchip_sfc_do_rd_wr(struct spi_nor *nor, loff_t from_to,
>> +				     size_t len, u_char *buf, u32 op_type)
>> +{
>> +	struct rockchip_sfc_chip_priv *priv = nor->priv;
>> +	struct rockchip_sfc *sfc = priv->sfc;
>> +	int ret;
>> +
>> +	if (!sfc->use_dma)
>> +		goto no_dma;
>> +
>> +	return rockchip_sfc_dma_transfer(nor, from_to, len,
>> +					 buf, op_type);
>
> if (likely(sfc->use_dma))
>   return rockchip_sfc_dma...();
>
> /* Comment saying that we fall back to PIO */
> ... pio code ...
>

sure.

>> +no_dma:
>> +	ret = rockchip_sfc_pio_transfer(nor, from_to, len,
>> +					(u_char *)buf, op_type);
>> +	if (ret) {
>> +		if (op_type == SFC_CMD_DIR_RD)
>> +			dev_warn(nor->dev, "PIO read timeout\n");
>> +		else
>> +			dev_warn(nor->dev, "PIO write timeout\n");
>> +		return ret;
>> +	}
>> +
>> +	return len;
>> +}
>
> [...]
>
>> +/**
>
> Drop this asterisk unless you document the driver in kerneldoc.

okay

>
>> + * Get spi flash device information and register it as a mtd device.
>> + */
>> +static int rockchip_sfc_register(struct device_node *np,
>> +				 struct rockchip_sfc *sfc)
>> +{
>> +	struct device *dev = sfc->dev;
>> +	struct mtd_info *mtd;
>> +	struct spi_nor *nor;
>> +	int ret;
>> +
>> +	nor = &(sfc->flash[sfc->num_chip].nor);
>
> Parenthesis not needed.

sure.

>
>> +	nor->dev = dev;
>> +	spi_nor_set_flash_node(nor, np);
>> +
>> +	ret = of_property_read_u8(np, "reg", &sfc->flash[sfc->num_chip].cs);
>> +	if (ret) {
>> +		dev_err(dev, "No reg property for %s\n",
>> +			np->full_name);
>> +		return ret;
>> +	}
>> +
>> +	ret = of_property_read_u32(np, "spi-max-frequency",
>> +			&sfc->flash[sfc->num_chip].clk_rate);
>> +	if (ret) {
>> +		dev_err(dev, "No spi-max-frequency property for %s\n",
>> +			np->full_name);
>> +		return ret;
>> +	}
>> +
>> +	sfc->flash[sfc->num_chip].sfc = sfc;
>> +	nor->priv = &(sfc->flash[sfc->num_chip]);
>> +
>> +	nor->prepare = rockchip_sfc_prep;
>> +	nor->unprepare = rockchip_sfc_unprep;
>> +	nor->read_reg = rockchip_sfc_read_reg;
>> +	nor->write_reg = rockchip_sfc_write_reg;
>> +	nor->read = rockchip_sfc_read;
>> +	nor->write = rockchip_sfc_write;
>> +	nor->erase = NULL;
>> +	ret = spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
>> +	if (ret)
>> +		return ret;
>> +
>> +	mtd = &(nor->mtd);
>> +	mtd->name = np->name;
>> +	ret = mtd_device_register(mtd, NULL, 0);
>> +	if (ret)
>> +		return ret;
>> +
>> +	sfc->num_chip++;
>> +	return 0;
>> +}
>> +
>> +static void rockchip_sfc_unregister_all(struct rockchip_sfc *sfc)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < sfc->num_chip; i++)
>> +		mtd_device_unregister(&(sfc->flash[i].nor.mtd));
>
> Inner parenthesis not needed IMO

okay.

>
>> +}
>> +
>> +static int rockchip_sfc_register_all(struct rockchip_sfc *sfc)
>> +{
>> +	struct device *dev = sfc->dev;
>> +	struct device_node *np;
>> +	int ret;
>> +
>> +	for_each_available_child_of_node(dev->of_node, np) {
>> +		ret = rockchip_sfc_register(np, sfc);
>> +		if (ret)
>> +			goto fail;
>> +
>> +		if (sfc->num_chip == SFC_MAX_CHIPSELECT_NUM) {
>> +			dev_warn(dev, "Exceeds the max cs limitation\n");
>> +			break;
>> +		}
>> +	}
>> +
>> +	return 0;
>> +
>> +fail:
>> +	dev_err(dev, "Failed to register all chips\n");
>> +	/* Unregister all the _registered_ nor flash */
>> +	rockchip_sfc_unregister_all(sfc);
>> +	return ret;
>> +}
>
>
> [...]
>
>> +#ifdef CONFIG_PM
>> +int rockchip_sfc_runtime_suspend(struct device *dev)
>> +{
>> +	struct rockchip_sfc *sfc = dev_get_drvdata(dev);
>> +
>> +	clk_disable_unprepare(sfc->hclk);
>> +	return 0;
>> +}
>
> Was the suspend ever really tested with this block ? Is disabling clock
> really enough ?

It was tested and we could do more, for instance power off the genpd,
but disabling clcok should be enough.

>
>> +int rockchip_sfc_runtime_resume(struct device *dev)
>> +{
>> +	struct rockchip_sfc *sfc = dev_get_drvdata(dev);
>> +
>> +	clk_prepare_enable(sfc->hclk);
>> +	return 0;
>> +}
>> +#endif /* CONFIG_PM */
>
> [...]
>


-- 
Best Regards
Shawn Lin




More information about the linux-mtd mailing list