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

From: Marek Vasut
Date: Wed Apr 06 2016 - 22:29:03 EST


On 04/07/2016 04:10 AM, Jiancheng Xue wrote:
> 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.

Would you please stop top-posting ? It rubs some people the wrong way.

> 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);

Is all the ad-hoc memcpying necessary? I think you can use
dma_map_single() and co to obtain DMAble memory for your
controller's use and then you can probably get rid of most
of this stuff.

> } 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@xxxxxxxxxx>
>>>>> Signed-off-by: Jiancheng Xue <xuejiancheng@xxxxxxxxxx>
>>>>> Acked-by: Rob Herring <robh@xxxxxxxxxx>
>>>>> Reviewed-by: Ezequiel Garcia <ezequiel@xxxxxxxxxxxxxxxxxxxx>
>>>>> Reviewed-by: Jagan Teki <jteki@xxxxxxxxxxxx>
>>>>> ---
>>>>> 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);
>>
>> .
>>
>


--
Best regards,
Marek Vasut