[RESEND PATCH v9] mtd: spi-nor: add hisilicon spi-nor flash controller driver

Jiancheng Xue xuejiancheng at huawei.com
Wed Apr 6 19:10:07 PDT 2016


Hi Brian,
   Thank you very much for your comments. I'll fix these issues in next version.
In addition, for easy understanding I'd like to rewrite hisi_spi_nor_write and
hisi_spi_nor_read. Your comments on these modifications will be highly appreciated.

static int hisi_spi_nor_read(struct spi_nor *nor, loff_t from, size_t len,
		size_t *retlen, u_char *read_buf)
{
	struct hifmc_priv *priv = nor->priv;
	struct hifmc_host *host = priv->host;
	int i;

	/* read all bytes in only one time */
	if (len <= HIFMC_DMA_MAX_LEN) {
		hisi_spi_nor_dma_transfer(nor, from, host->dma_buffer,
				len, FMC_OP_READ);
		memcpy(read_buf, host->buffer, len);
	} else {
		/* read HIFMC_DMA_MAX_LEN bytes at a time */
		for (i = 0; i < len; i += HIFMC_DMA_MAX_LEN) {
			hisi_spi_nor_dma_transfer(nor, from + i, host->dma_buffer,
				HIFMC_DMA_MAX_LEN, FMC_OP_READ);
			memcpy(read_buf + i, host->buffer, HIFMC_DMA_MAX_LEN);
		}
		/* read remaining bytes */
		i -= HIFMC_DMA_MAX_LEN;
		hisi_spi_nor_dma_transfer(nor, from + i, host->dma_buffer,
				len - i, FMC_OP_READ);
		memcpy(read_buf + i, host->buffer, len - i);
	}
	*retlen = len;

	return 0;
}

Because "len" passed from spi_nor_write is smaller than nor->page_size, and nor->page_size
is smaller than the length of host->dma_buffer. We can transfer "len" bytes data by
hisi_spi_nor_dma_transfer at one time. hisi_spi_nor_write can be simplified like below:

static void hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
			size_t len, size_t *retlen, const u_char *write_buf)
{
	struct hifmc_priv *priv = nor->priv;
	struct hifmc_host *host = priv->host;

	/* len is smaller than dma buffer length*/
	memcpy(host->buffer, write_buf, len);
	hisi_spi_nor_dma_transfer(nor, to, host->dma_buffer, len,
					FMC_OP_WRITE);

	*retlen = len;
}

Regards,
Jiancheng

On 2016/4/4 14:44, Brian Norris wrote:
> Hi Jiancheng,
> 
> Looking good. In addition to Marek's comments, I have just a few small
> comments. I'll post a small diff at the end, and a few inline comments.
> 
> On Mon, Mar 28, 2016 at 05:15:28PM +0800, Jiancheng Xue wrote:
>> Hi Marek,
>>     Firstly, thank you very much for your comments.
>>
>> On 2016/3/27 9:47, Marek Vasut wrote:
>>> On 03/26/2016 09:11 AM, Jiancheng Xue wrote:
>>>> Add hisilicon spi-nor flash controller driver
>>>>
>>>> Signed-off-by: Binquan Peng <pengbinquan at huawei.com>
>>>> Signed-off-by: Jiancheng Xue <xuejiancheng at huawei.com>
>>>> Acked-by: Rob Herring <robh at kernel.org>
>>>> Reviewed-by: Ezequiel Garcia <ezequiel at vanguardiasur.com.ar>
>>>> Reviewed-by: Jagan Teki <jteki at openedev.com>
>>>> ---
>>>> change log
>>>> v9:
>>>> Fixed issues pointed by Jagan Teki.
>>>
>>> It'd be really great if you could list which exact issues you fixed.
>>>
>>
>> OK. I'll describe the history log more detailed in next version.
>>
>>>> v8:
>>>> Fixed issues pointed by Ezequiel Garcia and Brian Norris.
>>>> Moved dts binding file to mtd directory.
>>>> Changed the compatible string more specific.
>>>
>>> [...]
> 
> ^^ You were using <linux/of_device.h> in here, even though you don't
> need any of its contents. You just wanted <linux/of.h> and
> <linux/platform_device.h>.
> 
>>>
>>>> +/* Hardware register offsets and field definitions */
>>>> +#define FMC_CFG				0x00
>>>> +#define SPI_NOR_ADDR_MODE		BIT(10)
>>>> +#define FMC_GLOBAL_CFG			0x04
>>>> +#define FMC_GLOBAL_CFG_WP_ENABLE	BIT(6)
>>>> +#define FMC_SPI_TIMING_CFG		0x08
>>>> +#define TIMING_CFG_TCSH(nr)		(((nr) & 0xf) << 8)
>>>> +#define TIMING_CFG_TCSS(nr)		(((nr) & 0xf) << 4)
>>>> +#define TIMING_CFG_TSHSL(nr)		((nr) & 0xf)
>>>> +#define CS_HOLD_TIME			0x6
>>>> +#define CS_SETUP_TIME			0x6
>>>> +#define CS_DESELECT_TIME		0xf
>>>> +#define FMC_INT				0x18
>>>> +#define FMC_INT_OP_DONE			BIT(0)
>>>> +#define FMC_INT_CLR			0x20
>>>> +#define FMC_CMD				0x24
>>>> +#define FMC_CMD_CMD1(_cmd)		((_cmd) & 0xff)
>>>
>>> Drop the underscores in the argument names.
>>>
>> OK.
>>>> +#define FMC_ADDRL			0x2c
>>>> +#define FMC_OP_CFG			0x30
>>>> +#define OP_CFG_FM_CS(_cs)		((_cs) << 11)
>>>> +#define OP_CFG_MEM_IF_TYPE(_type)	(((_type) & 0x7) << 7)
>>>> +#define OP_CFG_ADDR_NUM(_addr)		(((_addr) & 0x7) << 4)
>>>> +#define OP_CFG_DUMMY_NUM(_dummy)	((_dummy) & 0xf)
>>>> +#define FMC_DATA_NUM			0x38
>>>> +#define FMC_DATA_NUM_CNT(_n)		((_n) & 0x3fff)
>>>> +#define FMC_OP				0x3c
>>>> +#define FMC_OP_DUMMY_EN			BIT(8)
>>>> +#define FMC_OP_CMD1_EN			BIT(7)
>>>> +#define FMC_OP_ADDR_EN			BIT(6)
>>>> +#define FMC_OP_WRITE_DATA_EN		BIT(5)
>>>> +#define FMC_OP_READ_DATA_EN		BIT(2)
>>>> +#define FMC_OP_READ_STATUS_EN		BIT(1)
>>>> +#define FMC_OP_REG_OP_START		BIT(0)
>>>
>>> [...]
>>>
>>>> +enum hifmc_iftype {
>>>> +	IF_TYPE_STD,
>>>> +	IF_TYPE_DUAL,
>>>> +	IF_TYPE_DIO,
>>>> +	IF_TYPE_QUAD,
>>>> +	IF_TYPE_QIO,
>>>> +};
>>>> +
>>>> +struct hifmc_priv {
>>>> +	int chipselect;
>>>
>>> Can chipselect be set to < 0 values ?
>>>
>> The type will be changed to u32.
>>
>>>> +	u32 clkrate;
>>>> +	struct hifmc_host *host;
>>>> +};
>>>> +
>>>> +#define HIFMC_MAX_CHIP_NUM		2
>>>
>>> This does not scale very well, use dynamic allocation.
>>>
>> OK. Dynamic allocation will be used in next version.
>>>> +struct hifmc_host {
>>>> +	struct device *dev;
>>>> +	struct mutex lock;
>>>> +
>>>> +	void __iomem *regbase;
>>>> +	void __iomem *iobase;
>>>> +	struct clk *clk;
>>>> +	void *buffer;
>>>> +	dma_addr_t dma_buffer;
>>>> +
>>>> +	struct spi_nor	nor[HIFMC_MAX_CHIP_NUM];
>>>> +	struct hifmc_priv priv[HIFMC_MAX_CHIP_NUM];
>>>> +	int num_chip;
>>>> +};
>>>> +
>>>> +static inline int wait_op_finish(struct hifmc_host *host)
>>>> +{
>>>> +	unsigned int reg;
>>>> +
>>>> +	return readl_poll_timeout(host->regbase + FMC_INT, reg,
>>>> +		(reg & FMC_INT_OP_DONE), 0, FMC_WAIT_TIMEOUT);
>>>> +}
>>>> +
>>>> +static int get_if_type(enum read_mode flash_read)
>>>> +{
>>>> +	enum hifmc_iftype if_type;
>>>> +
>>>> +	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:
>>>> +	default:
>>>> +		if_type = IF_TYPE_STD;
>>>> +		break;
>>>> +	}
>>>> +
>>>> +	return if_type;
>>>> +}
>>>> +
>>>> +static void hisi_spi_nor_init(struct hifmc_host *host)
>>>> +{
>>>> +	unsigned int reg;
>>>
>>> Should be u32 here.
>>>
>> OK.
>>>> +	reg = TIMING_CFG_TCSH(CS_HOLD_TIME)
>>>> +		| TIMING_CFG_TCSS(CS_SETUP_TIME)
>>>> +		| TIMING_CFG_TSHSL(CS_DESELECT_TIME);
>>>> +	writel(reg, host->regbase + FMC_SPI_TIMING_CFG);
>>>> +}
>>>> +
>>>> +static int hisi_spi_nor_prep(struct spi_nor *nor, enum spi_nor_ops ops)
>>>> +{
>>>> +	struct hifmc_priv *priv = nor->priv;
>>>> +	struct hifmc_host *host = priv->host;
>>>> +	int ret;
>>>> +
>>>> +	mutex_lock(&host->lock);
>>>
>>> Why do you need the mutex lock here ? Let me guess -- SPI NOR framework
>>> locks a mutex in struct spi_nor , but that's not enough if you have
>>> multiple SPI NORs on the same bus, because concurrent access to multiple
>>> SPI NOR flashes needs locking on the controller level, right ?
>>>
>> Yes, you are quite right. The controller can connect with two SPI NOR flashes
>> on one board. This lock is used on the controller level.
> 
> Yeah... we should probably implement some common controller logic in the
> core eventually. But the mutex is necessary for now.
> 
>>>> +	ret = clk_set_rate(host->clk, priv->clkrate);
>>>> +	if (ret)
>>>> +		goto out;
>>>> +
>>>> +	ret = clk_prepare_enable(host->clk);
>>>> +	if (ret)
>>>> +		goto out;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +out:
>>>> +	mutex_unlock(&host->lock);
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void hisi_spi_nor_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
>>>> +{
>>>> +	struct hifmc_priv *priv = nor->priv;
>>>> +	struct hifmc_host *host = priv->host;
>>>> +
>>>> +	clk_disable_unprepare(host->clk);
>>>> +	mutex_unlock(&host->lock);
>>>> +}
>>>> +
>>>> +static void hisi_spi_nor_cmd_prepare(struct hifmc_host *host, u8 cmd,
>>>> +		u32 *opcfg)
>>>> +{
>>>> +	u32 reg;
>>>> +
>>>> +	*opcfg |= FMC_OP_CMD1_EN;
>>>> +	switch (cmd) {
>>>> +	case SPINOR_OP_RDID:
>>>> +	case SPINOR_OP_RDSR:
>>>> +	case SPINOR_OP_RDCR:
>>>> +		*opcfg |= FMC_OP_READ_DATA_EN;
>>>> +		break;
>>>> +	case SPINOR_OP_WREN:
>>>> +		reg = readl(host->regbase + FMC_GLOBAL_CFG);
>>>> +		if (reg & FMC_GLOBAL_CFG_WP_ENABLE) {
>>>> +			reg &= ~FMC_GLOBAL_CFG_WP_ENABLE;
>>>> +			writel(reg, host->regbase + FMC_GLOBAL_CFG);
>>>> +		}
>>>> +		break;
>>>> +	case SPINOR_OP_WRSR:
>>>> +		*opcfg |= FMC_OP_WRITE_DATA_EN;
>>>> +		break;
>>>> +	case SPINOR_OP_BE_4K:
>>>> +	case SPINOR_OP_BE_4K_PMC:
>>>> +	case SPINOR_OP_SE_4B:
>>>> +	case SPINOR_OP_SE:
>>>> +		*opcfg |= FMC_OP_ADDR_EN;
>>>> +		break;
>>>> +	case SPINOR_OP_EN4B:
>>>> +		reg = readl(host->regbase + FMC_CFG);
>>>> +		reg |= SPI_NOR_ADDR_MODE;
>>>> +		writel(reg, host->regbase + FMC_CFG);
>>>> +		break;
>>>> +	case SPINOR_OP_EX4B:
>>>> +		reg = readl(host->regbase + FMC_CFG);
>>>> +		reg &= ~SPI_NOR_ADDR_MODE;
>>>> +		writel(reg, host->regbase + FMC_CFG);
>>>> +		break;
>>>> +	case SPINOR_OP_CHIP_ERASE:
>>>> +	default:
>>>> +		break;
>>>> +	}
>>>
>>> Won't the driver fail if we add new instructions into the SPI NOR core
>>> which are not handled by this huge switch statement ?
>>>
>> Only some of commands are needed to process in this stage for the controller.
>> Others have no needs. So this function won't return failure.
> 
> It's not ideal to have this sort of function if we can avoid it (since
> it's hard to figure out how to extend this for new opcodes). But it
> looks necessary for now.
> 
>> I'll combine SPINOR_OP_CHIP_ERASE into the default case in next version.
>>
>>>> +}
>>>
>>> [...]
>>>
>>>> +static void hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
>>>> +			size_t len, size_t *retlen, const u_char *write_buf)
>>>> +{
>>>> +	struct hifmc_priv *priv = nor->priv;
>>>> +	struct hifmc_host *host = priv->host;
>>>> +	const unsigned char *ptr = write_buf;
>>>> +	size_t actual_len;
>>>> +
>>>> +	*retlen = 0;
>>>> +	while (len > 0) {
>>>> +		if (to & HIFMC_DMA_MASK)
>>>> +			actual_len = (HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK))
>>>> +				>= len	? len
>>>> +				: (HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK));
>>>
>>> Rewrite this as something like the following snippet, for the sake of
>>> readability:
>>>
>>> actual_len = HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK);
>>> if (actual_len >= len)
>>>    actual_len = len;
>>>
>> OK. Thank you.
>>>> +		else
>>>> +			actual_len = (len >= HIFMC_DMA_MAX_LEN)
>>>> +				? HIFMC_DMA_MAX_LEN : len;
> 
> Wait, why do you need the else case? Isn't this just equivalent to the
> first case? I'd suggest consolidating these two blocks, and dropping the
> ?: entirely, since (like Marek said) it's hurting readability. So:
> 
> 		/* Don't cross HIFMC_DMA_MAX_LEN alignment boundaries */
> 		if ((HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK)) >= len)
> 			actual_len = len;
> 		else
> 			actual_len = HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK);
> 
>>>> +		memcpy(host->buffer, ptr, actual_len);
>>>> +		hisi_spi_nor_dma_transfer(nor, to, host->dma_buffer, actual_len,
>>>> +				FMC_OP_WRITE);
>>>> +		to += actual_len;
>>>> +		ptr += actual_len;
>>>> +		len -= actual_len;
>>>> +		*retlen += actual_len;
>>>> +	}
>>>> +}
> 
> [...]
> 
> Here's my diff:
> 
> diff --git a/drivers/mtd/spi-nor/hisi-sfc.c b/drivers/mtd/spi-nor/hisi-sfc.c
> index e974c92a0a25..0c58fd3b8790 100644
> --- a/drivers/mtd/spi-nor/hisi-sfc.c
> +++ b/drivers/mtd/spi-nor/hisi-sfc.c
> @@ -16,13 +16,15 @@
>   * You should have received a copy of the GNU General Public License
>   * along with this program. If not, see <http://www.gnu.org/licenses/>.
>   */
> +
>  #include <linux/clk.h>
>  #include <linux/dma-mapping.h>
>  #include <linux/iopoll.h>
>  #include <linux/module.h>
>  #include <linux/mtd/mtd.h>
>  #include <linux/mtd/spi-nor.h>
> -#include <linux/of_platform.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
>  #include <linux/slab.h>
>  
>  /* Hardware register offsets and field definitions */
> @@ -343,13 +345,11 @@ static void hisi_spi_nor_write(struct spi_nor *nor, loff_t to,
>  
>  	*retlen = 0;
>  	while (len > 0) {
> -		if (to & HIFMC_DMA_MASK)
> -			actual_len = (HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK))
> -				>= len	? len
> -				: (HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK));
> +		/* Don't cross HIFMC_DMA_MAX_LEN alignment boundaries */
> +		if ((HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK)) >= len)
> +			actual_len = len;
>  		else
> -			actual_len = (len >= HIFMC_DMA_MAX_LEN)
> -				? HIFMC_DMA_MAX_LEN : len;
> +			actual_len = HIFMC_DMA_MAX_LEN - (to & HIFMC_DMA_MASK);
>  		memcpy(host->buffer, ptr, actual_len);
>  		hisi_spi_nor_dma_transfer(nor, to, host->dma_buffer, actual_len,
>  				FMC_OP_WRITE);
> 
> .
> 




More information about the linux-mtd mailing list