Re: [PATCH v10] mtd: spi-nor: add hisilicon spi-nor flash controller driver

From: Cyrille Pitchen
Date: Tue May 10 2016 - 07:34:53 EST


Hi Jiancheng,

Le 04/05/2016 10:25, Jiancheng Xue a écrit :
> Hi Cyrille,
>
> On 2016/4/27 19:55, Cyrille Pitchen wrote:
>> Hi Jiancheng,
>>
>> Le 19/04/2016 09:27, Jiancheng Xue a écrit :
>>> From: Jiancheng Xue <xuejiancheng@xxxxxxxxxx>
>>>
>>> Add hisilicon spi-nor flash controller driver
>>
>> [...]
>>> +enum hifmc_iftype {
>>> + IF_TYPE_STD,
>>> + IF_TYPE_DUAL,
>>> + IF_TYPE_DIO,
>>> + IF_TYPE_QUAD,
>>> + IF_TYPE_QIO,
>>> +};
>>
>> Just for my own information, the hisilicon controller supports:
>> - SPI 1-1-1 : IF_TYPE_STD
>> - SPI 1-1-2 : IF_TYPE_DUAL
>> - SPI 1-2-2 : IF_TYPE_DIO
>> - SPI 1-1-4 : IF_TYPE_QUAD
>> - SPI 1-4-4 : IF_TYPE_QIO
>>
>> but not the SPI protocols 2-2-2 or 4-4-4, does it?
>
> You are right.
>
>> If I ask you this question, it's because I wonder how to make the difference
>> between SPI controllers supporting both SPI 1-4-4 and 4-4-4 and those
>> supporting only SPI 1-4-4 in the case they rely on the m25p80 driver as an
>> adaptation layer between the spi-nor framework et the spi framework.
>>
>> I guess the "spi-tx-bus-width" and "spi-rx-bus-width" DT properties are not
>> enough to make the difference between these two kinds of SPI controller.
>>
>> I understand your driver doesn't use the m25p80 driver as it directly calls
>> spi_nor_scan() and implements the .read_reg, .write_reg, .read, .write and
>> .erase hooks, but its a general question :)
>>
> IMO, the "spi-tx-bus-width" and "spi-rx-bus-width" DT properties which
> will be stored in spi_device.mode just represent modes of the spi device,
> but not the ones of the spi controller. We should add a specific member for
> the controller to indicate all modes which it supports.
>
> The definition of spi_nor_scan can be modified to
> int spi_nor_scan(struct spi_nor *nor, const char *name, int mode).
> The argument "mode" represents all modes which the controller supports.
> For example,
> #define SPI_1_1_1 (1 << 0)
> #define SPI_1_1_4 (1 << 0)
> #define SPI_1_4_4 (1 << 2)
> #define SPI_4_4_4 (1 << 3)
> If the controller supports SPI_1_1_1, SPI_1_1_4 and SPI_1_4_4, the mode
> passed to spi_nor_scan will be (SPI_1_1_1 | SPI_1_1_4 | SPI_1_4_4).
>
> The really read_mode we use when transferring should depend on both controller's
> modes(i.e. the argument "mode") and device's modes(i.e. struct flash_info.flags,
> which can be got by spi_nor_read_id()).
>

I've sent few series of patches to the mailing list to enhance the support of
QSPI memories. The latest one was an RFC: I've implemented support of Micron
memories as an example. Since the publication, I've also written support for
Macronix but I didn't sent the Macronix patch yet. If you want to have a look
to the latest published version:

https://lkml.org/lkml/2016/4/13/633

>>> +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;
>>> +}
>>
>> Here I understand your QSPI controller could support SPI 1-4-4 and SPI 1-2-2
>> but you limit it to SPI 1-1-4 and SPI 1-1-2 because the spi-nor framework
>> doesn't support those protocols yet.
>>
> Yes. You're right.
> It is one of the reasons that the spi-nor framework doesn't support those two protocols.
> We also found protocols supported were enough for our product solutions now. So
> we didn't implement SPI-1-4-4 and SPI-1-2-2 in this patch.
>
>> [...]
>>
>>> +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;
>>> + }
>>> +}
>>
>> IMHO, this is exactly what should be avoided in (Q)SPI controller drivers:
>> they should not analyse the command op code to guess some settings such as
>> the SPI protocol to be used or in your case whether some address or data
>> cycles are included inside the command.
>>
>> Why? Just because your driver won't know what to do when we introduce new
>> op codes. Right now, hisi_spi_nor_cmd_prepare() is not aware of op code 0x15
>> use by Macronix to read its Configuration Register or Micron op codes to
>> read/write their Volatile Configuration Register and Enhanced Volatile
>> Configuration Register. So your driver won't support those memories any longer
>> when new features using those registers will be added in the spi-nor framework.
>>
>>
>> Since your driver implements struct spi_nor hooks, here is what you could do
>> instead:
>>
>> 1 - hisi_spi_nor_read_reg(..., u8 *buf, int len)
>>
>> if buf != NULL then you should set FMC_OP_READ_DATA_EN, don't set it otherwise.
>>
>>
>> 2 - hisi_spi_nor_write_reg(..., u8 *buf, int len)
>>
>> buf should always be != NULL so I guess you can always set FMC_OP_WRITE_DATA_EN
>>
>> For the special case of SPINOR_OP_WREN (Write Enable), your driver seems
>> to clear the FMC_GLOBAL_CFG_WP_ENABLE bit from the FMC_GLOBAL_CFG register, if
>> set, but never set it again... So why not clear this bit once for all?
>>
>> Reading the current source code, the support of the hardware write protect
>> feature is a little bit odd, isn't it?
>>
>> 3 - hisi_spi_nor_read(struct spi_nor *nor, ...)
>>
>> your driver doesn't seem to call hisi_spi_nor_send_cmd() /
>> hisi_spi_nor_cmd_prepare() from _read() so I don't know whether you have to
>> set the FMC_OP_READ_DATA_EN bit here.
>>
>> Also you can check nor->addr_width to know whether you need SPI_NOR_ADDR_MODE.
>>
>>
>> 4 - hisi_spi_nor_write(struct spi_nor *nor, ...)
>>
>> Almost the same comment as for _read(): just replace FMC_OP_READ_DATA_EN by
>> FMC_OP_WRITE_DATA_EN I guess.
>>
>>
>> 5 - hisi_spi_nor_erase(struct spi_nor *nor, ...)
>>
>> Here again you should check nor->addr_width to know when to set the
>> SPI_NOR_ADDR_MODE bit in the FMC_CFG register.
>>
>> The FMC_OP_ADDR_EN bit should always be set for erase operations.
>>
>>
>>> +static int hisi_spi_nor_send_cmd(struct spi_nor *nor, u8 cmd, int len)
>>> +{
>>> + struct hifmc_priv *priv = nor->priv;
>>> + struct hifmc_host *host = priv->host;
>>> + u32 reg, op_cfg = 0;
>>> +
>>> + hisi_spi_nor_cmd_prepare(host, cmd, &op_cfg);
>> [...]
>>
>> Just get rid of hisi_spi_nor_cmd_prepare(), it would simplify the support of
>> your driver a lot. For sure, I don't know the hisilicon spi-nor flash
>> controller as much as you do but I'm pretty sure its driver doesn't need to
>> analyse the op code to guess some hardware settings. There are other means to
>> find out these settings.
>>
>> Anyway, I hope these few comments will help you.
>>
> Thank you very much for your detailed comments. They help me a lot. I looked into
> the datasheet and this driver. I found it was really a big mistake for
> hisi_spi_nor_cmd_prepare to configure registers according to the command op code.
>
> I rewrote corresponding functions like below.
>

This new version looks good to me. I just have one small suggestion:

> static int hisi_spi_nor_op_reg(struct spi_nor *nor, u8 opcode, int len, u8 optype)
> {
> struct hifmc_priv *priv = nor->priv;
> struct hifmc_host *host = priv->host;
> u32 reg;
>
> reg = FMC_CMD_CMD1(opcode);
> writel(reg, host->regbase + FMC_CMD);
>
> reg = FMC_DATA_NUM_CNT(len);
> writel(reg, host->regbase + FMC_DATA_NUM);
>
> reg = OP_CFG_FM_CS(priv->chipselect);
> writel(reg, host->regbase + FMC_OP_CFG);
>
> writel(0xff, host->regbase + FMC_INT_CLR);
> reg = FMC_OP_CMD1_EN;
> if (optype == FMC_OP_READ) {
> reg |= FMC_OP_READ_DATA_EN;
> } else {
> reg |= FMC_OP_WRITE_DATA_EN;
> }
Assuming you provide the right value in 'optype', here you could directly
write:
- if (optype == FMC_OP_READ) {
- reg |= FMC_OP_READ_DATA_EN;
- } else {
- reg |= FMC_OP_WRITE_DATA_EN;
- }
+ reg |= optype;


> reg |= FMC_OP_REG_OP_START;
> writel(reg, host->regbase + FMC_OP);
>
> return wait_op_finish(host);
> }
>
> static int hisi_spi_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
> int len)
> {
> struct hifmc_priv *priv = nor->priv;
> struct hifmc_host *host = priv->host;
> int ret;
>
> ret = hisi_spi_nor_op_reg(nor, opcode, len, FMC_OP_READ);
- ret = hisi_spi_nor_op_reg(nor, opcode, len, FMC_OP_READ);
+ ret = hisi_spi_nor_op_reg(nor, opcode, len, FMC_OP_READ_DATA_EN);
> if (ret)
> return ret;
>
> memcpy_fromio(buf, host->iobase, len);
> return 0;
> }
>
> static int hisi_spi_nor_write_reg(struct spi_nor *nor, u8 opcode,
> u8 *buf, int len)
> {
> struct hifmc_priv *priv = nor->priv;
> struct hifmc_host *host = priv->host;
>
> if (len)
> memcpy_toio(host->iobase, buf, len);
>
> return hisi_spi_nor_op_reg(nor, opcode, len, FMC_OP_WRITE);
- return hisi_spi_nor_op_reg(nor, opcode, len, FMC_OP_WRITE);
+ return hisi_spi_nor_op_reg(nor, opcode, len, FMC_OP_WRITE_DATA_EN);
> }
>
> static int hisi_spi_nor_register(struct device_node *np,
> struct hifmc_host *host)
> {
> struct spi_nor *nor;
> ...
> nor->read_reg = hisi_spi_nor_read_reg;
> nor->write_reg = hisi_spi_nor_write_reg;
> nor->erase = NULL; /*use the default implementation*/
> ...
> spi_nor_scan(nor, NULL, SPI_NOR_QUAD);
> ...
> }
>
> Any new comments will be highly appreciated. Thank you.
>
> Regards,
> Jiancheng
>
>
Best regards,

Cyrille