diff mbox

[PATCHv2] drivers: mtd: devices: Add quad read support.

Message ID 1380191565-28640-1-git-send-email-sourav.poddar@ti.com
State New, archived
Headers show

Commit Message

Poddar, Sourav Sept. 26, 2013, 10:32 a.m. UTC
Some flash also support quad read mode.
Adding support for adding quad mode in m25p80.

Signed-off-by: Sourav Poddar <sourav.poddar@ti.com>
---
v1->v2:
- Make the code more modular
- put proper error checks
 drivers/mtd/devices/m25p80.c |  155 +++++++++++++++++++++++++++++++++++++++---
 1 files changed, 144 insertions(+), 11 deletions(-)

Comments

David Woodhouse Sept. 26, 2013, 11 a.m. UTC | #1
On Thu, 2013-09-26 at 16:02 +0530, Sourav Poddar wrote:
> Some flash also support quad read mode.
> Adding support for adding quad mode in m25p80.
> 
> Signed-off-by: Sourav Poddar <sourav.poddar@ti.com>

This seems sane enough to me if Mark agrees that it's correct from the
SPI point of view.

Do we have a solution for the vf610-twr host controller, and is this
going to work for that too? Or are we still stuck on questions like "how
are we going to program the LUT to do what the request asks?" and "how
*does* the request actually hand the required information to the
controller?"

I've seen a bunch of "*this* is how you indicate dummy cycles", and
"*this* is how you request a quad-read transfer" type responses; have we
got all the way there yet?
Poddar, Sourav Sept. 26, 2013, 11:21 a.m. UTC | #2
Hi David,
On Thursday 26 September 2013 04:30 PM, David Woodhouse wrote:
> On Thu, 2013-09-26 at 16:02 +0530, Sourav Poddar wrote:
>> Some flash also support quad read mode.
>> Adding support for adding quad mode in m25p80.
>>
>> Signed-off-by: Sourav Poddar<sourav.poddar@ti.com>
> This seems sane enough to me if Mark agrees that it's correct from the
> SPI point of view.
>
> Do we have a solution for the vf610-twr host controller, and is this
> going to work for that too? Or are we still stuck on questions like "how
> are we going to program the LUT to do what the request asks?" and "how
> *does* the request actually hand the required information to the
> controller?"
>
If the pupose of LUT is to just set the dummy cycles, and vf610-twr
hardware state machine does not have have any other dependency
on LUT, this patch should work.

Yes, this patch also solves the issue of communicating the appropriate 
request
to the handler through some spi transfer parameters already available in 
spi.c.

As explained in one comment in my previous version,
If you see spi.c, "spi-rx-bus-width" property is already added. We will 
set this
property in dts to 4 in case of quad read. Once this is set, spi->mode will
be set to SPI_RX_QUAD in spi.c. which can be used by mtd layer to decide 
whether
its a quad read or not.

Then, we can use tx_nbits/rx_nbits property of spi_transfer to 
communicate to the
controller whether the ongoing transfer is a quad/single read/write.
> I've seen a bunch of "*this* is how you indicate dummy cycles", and
> "*this* is how you request a quad-read transfer" type responses; have we
> got all the way there yet?
>
David Woodhouse Sept. 26, 2013, 11:31 a.m. UTC | #3
On Thu, 2013-09-26 at 16:51 +0530, Sourav Poddar wrote:
> Yes, this patch also solves the issue of communicating the appropriate
> request to the handler through some spi transfer parameters already
> available in spi.c.

Great, thanks for confirming that.

So making vf610-twr conform to the Linux SPI API and do the right thing
according to the requests it receives is now Someone Else's Problem™ and
I look forward to never having to think about it again.

Have fun :)
Mark Brown Sept. 26, 2013, 11:59 a.m. UTC | #4
On Thu, Sep 26, 2013 at 12:00:43PM +0100, David Woodhouse wrote:

> This seems sane enough to me if Mark agrees that it's correct from the
> SPI point of view.

I didn't really look at the patch but since it doesn't involve changes
to the SPI API providing it does the right thing it should be fine.

> I've seen a bunch of "*this* is how you indicate dummy cycles", and
> "*this* is how you request a quad-read transfer" type responses; have we
> got all the way there yet?

Nobody added an API for dummy cycles yet but it's fairly obvious how to
add them if someone wants to do that.  The quad read stuff is in
mainline already.
Huang Shijie Sept. 27, 2013, 2:40 a.m. UTC | #5
于 2013年09月26日 19:21, Sourav Poddar 写道:
> If the pupose of LUT is to just set the dummy cycles, and vf610-twr
> hardware state machine does not have have any other dependency
> on LUT, this patch should work.
Hi Sourav & David:

    The key issue about the vf610-twr is that:
    [0] Use the LUT makes the Quadspi driver more efficiency.
    [1] the vf610-twr needs to know the SPI NOR commands for Page Program.
        Why? because the driver can not change the size of write-buffer 
from 256bytes to the 64byte(TXFIFO SIZE).
    [2] the dummy and other things.

Mark said the Quadspi is not a SPI controller, instead it is a SPI NOR 
controller, and Mark suggested me
  to rewrite the m25p80.c for the Quadspi.

thanks
Huang Shijie
Poddar, Sourav Sept. 27, 2013, 5:54 a.m. UTC | #6
On Friday 27 September 2013 08:10 AM, Huang Shijie wrote:
> 于 2013年09月26日 19:21, Sourav Poddar 写道:
>> If the pupose of LUT is to just set the dummy cycles, and vf610-twr
>> hardware state machine does not have have any other dependency
>> on LUT, this patch should work.
> Hi Sourav & David:
>
>    The key issue about the vf610-twr is that:
>    [0] Use the LUT makes the Quadspi driver more efficiency.
>    [1] the vf610-twr needs to know the SPI NOR commands for Page Program.
>        Why? because the driver can not change the size of write-buffer 
> from 256bytes to the 64byte(TXFIFO SIZE).
Not clear about this. But, you will anyway know what you are using from 
the m25p80 side rite?
>    [2] the dummy and other things.
>
Dummy stuffs can be handled from m25p80 side rite? fast read dummy cycle 
support is already there. While,
$subject patch adds it for quad read.
> Mark said the Quadspi is not a SPI controller, instead it is a SPI NOR 
> controller, and Mark suggested me
>  to rewrite the m25p80.c for the Quadspi.
>
> thanks
> Huang Shijie
>
>
Huang Shijie Sept. 27, 2013, 6:16 a.m. UTC | #7
于 2013年09月27日 13:54, Sourav Poddar 写道:
> On Friday 27 September 2013 08:10 AM, Huang Shijie wrote:
>> 于 2013年09月26日 19:21, Sourav Poddar 写道:
>>> If the pupose of LUT is to just set the dummy cycles, and vf610-twr
>>> hardware state machine does not have have any other dependency
>>> on LUT, this patch should work.
>> Hi Sourav & David:
>>
>>    The key issue about the vf610-twr is that:
>>    [0] Use the LUT makes the Quadspi driver more efficiency.
>>    [1] the vf610-twr needs to know the SPI NOR commands for Page 
>> Program.
>>        Why? because the driver can not change the size of 
>> write-buffer from 256bytes to the 64byte(TXFIFO SIZE).
> Not clear about this. But, you will anyway know what you are using 
> from the m25p80 side rite?

  The TX FIFO is 64 bytes in the Vybrid, but the Page Program may writes 
265 bytes per time.

  If the TX FIFO is smaller then the size of Page Program, we have to 
wait until the Write(64bytes) is finished.
  If we do not wait, the write will not finished.



>>    [2] the dummy and other things.
>>
> Dummy stuffs can be handled from m25p80 side rite? fast read dummy 
> cycle support is already there. While,
> $subject patch adds it for quad read.
The m25p80 can only handle the 8bit dummy now, such as fast read and 
QOR(0x6b).

But it can not handle the QIOR (0XEB, may needs 4bit dummy), and can not 
handle the DDR QIOR(0XED, may
needs 6bit dummy).

thanks
Huang Shijie
Poddar, Sourav Sept. 27, 2013, 6:27 a.m. UTC | #8
On Friday 27 September 2013 11:46 AM, Huang Shijie wrote:
> 于 2013年09月27日 13:54, Sourav Poddar 写道:
>> On Friday 27 September 2013 08:10 AM, Huang Shijie wrote:
>>> 于 2013年09月26日 19:21, Sourav Poddar 写道:
>>>> If the pupose of LUT is to just set the dummy cycles, and vf610-twr
>>>> hardware state machine does not have have any other dependency
>>>> on LUT, this patch should work.
>>> Hi Sourav & David:
>>>
>>>    The key issue about the vf610-twr is that:
>>>    [0] Use the LUT makes the Quadspi driver more efficiency.
>>>    [1] the vf610-twr needs to know the SPI NOR commands for Page 
>>> Program.
>>>        Why? because the driver can not change the size of 
>>> write-buffer from 256bytes to the 64byte(TXFIFO SIZE).
>> Not clear about this. But, you will anyway know what you are using 
>> from the m25p80 side rite?
>
>  The TX FIFO is 64 bytes in the Vybrid, but the Page Program may 
> writes 265 bytes per time.
>
>  If the TX FIFO is smaller then the size of Page Program, we have to 
> wait until the Write(64bytes) is finished.
>  If we do not wait, the write will not finished.
>
>
hmm..I think thats should be handle in your controller by checking 
t->tx_buf and see what command is used.
>
>>>    [2] the dummy and other things.
>>>
>> Dummy stuffs can be handled from m25p80 side rite? fast read dummy 
>> cycle support is already there. While,
>> $subject patch adds it for quad read.
> The m25p80 can only handle the 8bit dummy now, such as fast read and 
> QOR(0x6b).
>
> But it can not handle the QIOR (0XEB, may needs 4bit dummy), and can 
> not handle the DDR QIOR(0XED, may
> needs 6bit dummy).
>
Correct, as I explained in one of my comment on v1, that the creating of 
a quad api is justified for this reason itself, since
there are other quad commands wth different dummy cycle requirements. We 
can build on top of $subject patch and try
to add support for other quad commands also. ?
> thanks
> Huang Shijie
>
Huang Shijie Sept. 27, 2013, 7:05 a.m. UTC | #9
于 2013年09月27日 14:27, Sourav Poddar 写道:
> hmm..I think thats should be handle in your controller by checking 
> t->tx_buf and see what command is used. 
Just as Mark ever said:
    Parsing out the command makes the QuadSPI driver looks like a SPI 
NOR driver, not a SPI driver.
    And that's why i need to rewrite the m25p80 code.


Btw: i do not object your patch, i hope it can be merged as soon as 
possible.

thanks
Huang Shijie
Poddar, Sourav Oct. 7, 2013, 12:11 p.m. UTC | #10
Hi Brian,
On Thursday 26 September 2013 04:02 PM, Sourav Poddar wrote:
> Some flash also support quad read mode.
> Adding support for adding quad mode in m25p80.
>
> Signed-off-by: Sourav Poddar<sourav.poddar@ti.com>
> ---
> v1->v2:
> - Make the code more modular
> - put proper error checks
>   drivers/mtd/devices/m25p80.c |  155 +++++++++++++++++++++++++++++++++++++++---
>   1 files changed, 144 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
> index 26b14f9..3c8a794 100644
> --- a/drivers/mtd/devices/m25p80.c
> +++ b/drivers/mtd/devices/m25p80.c
> @@ -41,6 +41,7 @@
>   #define	OPCODE_WRSR		0x01	/* Write status register 1 byte */
>   #define	OPCODE_NORM_READ	0x03	/* Read data bytes (low frequency) */
>   #define	OPCODE_FAST_READ	0x0b	/* Read data bytes (high frequency) */
> +#define	OPCODE_QUAD_READ	0x6b	/* QUAD READ */
>   #define	OPCODE_PP		0x02	/* Page program (up to 256 bytes) */
>   #define	OPCODE_BE_4K		0x20	/* Erase 4KiB block */
>   #define	OPCODE_BE_4K_PMC	0xd7	/* Erase 4KiB block on PMC chips */
> @@ -48,10 +49,12 @@
>   #define	OPCODE_CHIP_ERASE	0xc7	/* Erase whole flash chip */
>   #define	OPCODE_SE		0xd8	/* Sector erase (usually 64KiB) */
>   #define	OPCODE_RDID		0x9f	/* Read JEDEC ID */
> +#define	OPCODE_RDCR		0x35	/* Read configuration register */
>
>   /* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
>   #define	OPCODE_NORM_READ_4B	0x13	/* Read data bytes (low frequency) */
>   #define	OPCODE_FAST_READ_4B	0x0c	/* Read data bytes (high frequency) */
> +#define	OPCODE_QUAD_READ_4B     0x6c    /* Read data bytes */
>   #define	OPCODE_PP_4B		0x12	/* Page program (up to 256 bytes) */
>   #define	OPCODE_SE_4B		0xdc	/* Sector erase (usually 64KiB) */
>
> @@ -76,6 +79,9 @@
>   #define	SR_BP2			0x10	/* Block protect 2 */
>   #define	SR_SRWD			0x80	/* SR write protect */
>
> +/* Configuration Register bits. */
> +#define	QUAD_CR_EN		0x2	/* Quad I/O */
> +
>   /* Define max times to check status register before we give up. */
>   #define	MAX_READY_WAIT_JIFFIES	(40 * HZ)	/* M25P16 specs 40s max chip erase */
>   #define	MAX_CMD_SIZE		5
> @@ -95,6 +101,7 @@ struct m25p {
>   	u8			program_opcode;
>   	u8			*command;
>   	bool			fast_read;
> +	bool			quad_read;
>   };
>
>   static inline struct m25p *mtd_to_m25p(struct mtd_info *mtd)
> @@ -163,6 +170,25 @@ static inline int write_disable(struct m25p *flash)
>   	return spi_write_then_read(flash->spi,&code, 1, NULL, 0);
>   }
>
> +/* Read the configuration register, returning its value in the location
> + * Return the configuration register value.
> + * Returns negative if error occurred.
> +*/
> +static int read_cr(struct m25p *flash)
> +{
> +	u8 code = OPCODE_RDCR;
> +	int ret;
> +	u8 val;
> +
> +	ret = spi_write_then_read(flash->spi,&code, 1,&val, 1);
> +	if (ret<  0) {
> +		dev_err(&flash->spi->dev, "error %d reading CR\n", ret);
> +		return ret;
> +	}
> +
> +	return val;
> +}
> +
>   /*
>    * Enable/disable 4-byte addressing mode.
>    */
> @@ -336,6 +362,97 @@ static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr)
>   	return 0;
>   }
>
> +/* Write status register and configuration register with 2 bytes
> +* The first byte will be written to the status register, while the second byte
> +* will be written to the configuration register.
> +* Returns negative if error occurred.
> +*/
> +static int write_sr_cr(struct m25p *flash, u16 val)
> +{
> +	flash->command[0] = OPCODE_WRSR;
> +	flash->command[1] = val&  0xff;
> +	flash->command[2] = (val>>  8);
> +
> +	return spi_write(flash->spi, flash->command, 3);
> +}
> +
> +static int quad_enable(struct m25p *flash)
> +{
> +	int ret;
> +	int quad_en = QUAD_CR_EN<<  8;
> +
> +	write_enable(flash);
> +
> +	ret = write_sr_cr(flash, quad_en);
> +	if (ret<  0) {
> +		dev_err(&flash->spi->dev,
> +			"error while writing configuration register");
> +		return -EINVAL;
> +	}
> +
> +	/* read back and check it */
> +	ret = read_cr(flash);
> +	if (!(ret>  0&&  (ret&  QUAD_CR_EN))) {
> +		dev_err(&flash->spi->dev,
> +			"Quad bit not set");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int m25p80_quad_read(struct mtd_info *mtd, loff_t from, size_t len,
> +	size_t *retlen, u_char *buf)
> +{
> +	struct m25p *flash = mtd_to_m25p(mtd);
> +	struct spi_transfer t[2];
> +	struct spi_message m;
> +	uint8_t opcode;
> +
> +	pr_debug("%s: %s from 0x%08x, len %zd\n", dev_name(&flash->spi->dev),
> +			__func__, (u32)from, len);
> +
> +	spi_message_init(&m);
> +	memset(t, 0, (sizeof(t)));
> +
> +	t[0].tx_buf = flash->command;
> +	t[0].len = m25p_cmdsz(flash) + (flash->quad_read ? 1 : 0);
> +	spi_message_add_tail(&t[0],&m);
> +
> +	t[1].rx_buf = buf;
> +	t[1].len = len;
> +	t[1].rx_nbits = SPI_NBITS_QUAD;
> +	spi_message_add_tail(&t[1],&m);
> +
> +	mutex_lock(&flash->lock);
> +
> +	/* Wait till previous write/erase is done. */
> +	if (wait_till_ready(flash)) {
> +		/* REVISIT status return?? */
> +		mutex_unlock(&flash->lock);
> +		return 1;
> +	}
> +
> +	/* FIXME switch to OPCODE_QUAD_READ.  It's required for higher
> +	 * clocks; and at this writing, every chip this driver handles
> +	 * supports that opcode.
> +	*/
> +
> +	/* Set up the write data buffer. */
> +	opcode = flash->read_opcode;
> +	flash->command[0] = opcode;
> +	m25p_addr2cmd(flash, from, flash->command);
> +
> +	spi_sync(flash->spi,&m);
> +
> +	*retlen = m.actual_length - m25p_cmdsz(flash) -
> +			(flash->quad_read ? 1 : 0);
> +
> +	mutex_unlock(&flash->lock);
> +
> +	return 0;
> +}
> +
>   /*
>    * Read an address range from the flash chip.  The address range
>    * may be any size provided it is within the physical boundaries.
> @@ -928,6 +1045,7 @@ static int m25p_probe(struct spi_device *spi)
>   	unsigned			i;
>   	struct mtd_part_parser_data	ppdata;
>   	struct device_node __maybe_unused *np = spi->dev.of_node;
> +	int ret;
>
>   #ifdef CONFIG_MTD_OF_PARTS
>   	if (!of_device_is_available(np))
> @@ -979,15 +1097,9 @@ static int m25p_probe(struct spi_device *spi)
>   		}
>   	}
>
> -	flash = kzalloc(sizeof *flash, GFP_KERNEL);
> +	flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
>   	if (!flash)
>   		return -ENOMEM;
> -	flash->command = kmalloc(MAX_CMD_SIZE + (flash->fast_read ? 1 : 0),
> -					GFP_KERNEL);
> -	if (!flash->command) {
> -		kfree(flash);
> -		return -ENOMEM;
> -	}
>
>   	flash->spi = spi;
>   	mutex_init(&flash->lock);
> @@ -1015,7 +1127,6 @@ static int m25p_probe(struct spi_device *spi)
>   	flash->mtd.flags = MTD_CAP_NORFLASH;
>   	flash->mtd.size = info->sector_size * info->n_sectors;
>   	flash->mtd._erase = m25p80_erase;
> -	flash->mtd._read = m25p80_read;
>
>   	/* flash protection support for STmicro chips */
>   	if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
> @@ -1067,6 +1178,28 @@ static int m25p_probe(struct spi_device *spi)
>
>   	flash->program_opcode = OPCODE_PP;
>
> +	flash->quad_read = false;
> +	if (spi->mode&&  SPI_RX_QUAD)
> +		flash->quad_read = true;
> +
> +	flash->command = kmalloc(MAX_CMD_SIZE + (flash->fast_read ? 1 :
> +				(flash->quad_read ? 1 : 0)), GFP_KERNEL);
> +	if (!flash->command) {
> +		kfree(flash);
> +		return -ENOMEM;
> +	}
> +
> +	if (flash->quad_read) {
> +		ret = quad_enable(flash);
> +		if (ret) {
> +			dev_err(&spi->dev,
> +				"error enabling quad");
> +			return -EINVAL;
> +		}
> +		flash->mtd._read = m25p80_quad_read;
> +	} else
> +		flash->mtd._read = m25p80_read;
> +
>   	if (info->addr_width)
>   		flash->addr_width = info->addr_width;
>   	else if (flash->mtd.size>  0x1000000) {
> @@ -1074,9 +1207,9 @@ static int m25p_probe(struct spi_device *spi)
>   		flash->addr_width = 4;
>   		if (JEDEC_MFR(info->jedec_id) == CFI_MFR_AMD) {
>   			/* Dedicated 4-byte command set */
> -			flash->read_opcode = flash->fast_read ?
> -				OPCODE_FAST_READ_4B :
> -				OPCODE_NORM_READ_4B;
> +			flash->read_opcode = (flash->fast_read ?
> +				OPCODE_FAST_READ_4B : (flash->quad_read ?
> +				OPCODE_QUAD_READ_4B : OPCODE_NORM_READ_4B));
>   			flash->program_opcode = OPCODE_PP_4B;
>   			/* No small sector erase for 4-byte command set */
>   			flash->erase_opcode = OPCODE_SE_4B;
Gentle Ping on this..
diff mbox

Patch

diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
index 26b14f9..3c8a794 100644
--- a/drivers/mtd/devices/m25p80.c
+++ b/drivers/mtd/devices/m25p80.c
@@ -41,6 +41,7 @@ 
 #define	OPCODE_WRSR		0x01	/* Write status register 1 byte */
 #define	OPCODE_NORM_READ	0x03	/* Read data bytes (low frequency) */
 #define	OPCODE_FAST_READ	0x0b	/* Read data bytes (high frequency) */
+#define	OPCODE_QUAD_READ	0x6b	/* QUAD READ */
 #define	OPCODE_PP		0x02	/* Page program (up to 256 bytes) */
 #define	OPCODE_BE_4K		0x20	/* Erase 4KiB block */
 #define	OPCODE_BE_4K_PMC	0xd7	/* Erase 4KiB block on PMC chips */
@@ -48,10 +49,12 @@ 
 #define	OPCODE_CHIP_ERASE	0xc7	/* Erase whole flash chip */
 #define	OPCODE_SE		0xd8	/* Sector erase (usually 64KiB) */
 #define	OPCODE_RDID		0x9f	/* Read JEDEC ID */
+#define	OPCODE_RDCR		0x35	/* Read configuration register */
 
 /* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
 #define	OPCODE_NORM_READ_4B	0x13	/* Read data bytes (low frequency) */
 #define	OPCODE_FAST_READ_4B	0x0c	/* Read data bytes (high frequency) */
+#define	OPCODE_QUAD_READ_4B     0x6c    /* Read data bytes */
 #define	OPCODE_PP_4B		0x12	/* Page program (up to 256 bytes) */
 #define	OPCODE_SE_4B		0xdc	/* Sector erase (usually 64KiB) */
 
@@ -76,6 +79,9 @@ 
 #define	SR_BP2			0x10	/* Block protect 2 */
 #define	SR_SRWD			0x80	/* SR write protect */
 
+/* Configuration Register bits. */
+#define	QUAD_CR_EN		0x2	/* Quad I/O */
+
 /* Define max times to check status register before we give up. */
 #define	MAX_READY_WAIT_JIFFIES	(40 * HZ)	/* M25P16 specs 40s max chip erase */
 #define	MAX_CMD_SIZE		5
@@ -95,6 +101,7 @@  struct m25p {
 	u8			program_opcode;
 	u8			*command;
 	bool			fast_read;
+	bool			quad_read;
 };
 
 static inline struct m25p *mtd_to_m25p(struct mtd_info *mtd)
@@ -163,6 +170,25 @@  static inline int write_disable(struct m25p *flash)
 	return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
 }
 
+/* Read the configuration register, returning its value in the location
+ * Return the configuration register value.
+ * Returns negative if error occurred.
+*/
+static int read_cr(struct m25p *flash)
+{
+	u8 code = OPCODE_RDCR;
+	int ret;
+	u8 val;
+
+	ret = spi_write_then_read(flash->spi, &code, 1, &val, 1);
+	if (ret < 0) {
+		dev_err(&flash->spi->dev, "error %d reading CR\n", ret);
+		return ret;
+	}
+
+	return val;
+}
+
 /*
  * Enable/disable 4-byte addressing mode.
  */
@@ -336,6 +362,97 @@  static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr)
 	return 0;
 }
 
+/* Write status register and configuration register with 2 bytes
+* The first byte will be written to the status register, while the second byte
+* will be written to the configuration register.
+* Returns negative if error occurred.
+*/
+static int write_sr_cr(struct m25p *flash, u16 val)
+{
+	flash->command[0] = OPCODE_WRSR;
+	flash->command[1] = val & 0xff;
+	flash->command[2] = (val >> 8);
+
+	return spi_write(flash->spi, flash->command, 3);
+}
+
+static int quad_enable(struct m25p *flash)
+{
+	int ret;
+	int quad_en = QUAD_CR_EN << 8;
+
+	write_enable(flash);
+
+	ret = write_sr_cr(flash, quad_en);
+	if (ret < 0) {
+		dev_err(&flash->spi->dev,
+			"error while writing configuration register");
+		return -EINVAL;
+	}
+
+	/* read back and check it */
+	ret = read_cr(flash);
+	if (!(ret > 0 && (ret & QUAD_CR_EN))) {
+		dev_err(&flash->spi->dev,
+			"Quad bit not set");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int m25p80_quad_read(struct mtd_info *mtd, loff_t from, size_t len,
+	size_t *retlen, u_char *buf)
+{
+	struct m25p *flash = mtd_to_m25p(mtd);
+	struct spi_transfer t[2];
+	struct spi_message m;
+	uint8_t opcode;
+
+	pr_debug("%s: %s from 0x%08x, len %zd\n", dev_name(&flash->spi->dev),
+			__func__, (u32)from, len);
+
+	spi_message_init(&m);
+	memset(t, 0, (sizeof(t)));
+
+	t[0].tx_buf = flash->command;
+	t[0].len = m25p_cmdsz(flash) + (flash->quad_read ? 1 : 0);
+	spi_message_add_tail(&t[0], &m);
+
+	t[1].rx_buf = buf;
+	t[1].len = len;
+	t[1].rx_nbits = SPI_NBITS_QUAD;
+	spi_message_add_tail(&t[1], &m);
+
+	mutex_lock(&flash->lock);
+
+	/* Wait till previous write/erase is done. */
+	if (wait_till_ready(flash)) {
+		/* REVISIT status return?? */
+		mutex_unlock(&flash->lock);
+		return 1;
+	}
+
+	/* FIXME switch to OPCODE_QUAD_READ.  It's required for higher
+	 * clocks; and at this writing, every chip this driver handles
+	 * supports that opcode.
+	*/
+
+	/* Set up the write data buffer. */
+	opcode = flash->read_opcode;
+	flash->command[0] = opcode;
+	m25p_addr2cmd(flash, from, flash->command);
+
+	spi_sync(flash->spi, &m);
+
+	*retlen = m.actual_length - m25p_cmdsz(flash) -
+			(flash->quad_read ? 1 : 0);
+
+	mutex_unlock(&flash->lock);
+
+	return 0;
+}
+
 /*
  * Read an address range from the flash chip.  The address range
  * may be any size provided it is within the physical boundaries.
@@ -928,6 +1045,7 @@  static int m25p_probe(struct spi_device *spi)
 	unsigned			i;
 	struct mtd_part_parser_data	ppdata;
 	struct device_node __maybe_unused *np = spi->dev.of_node;
+	int ret;
 
 #ifdef CONFIG_MTD_OF_PARTS
 	if (!of_device_is_available(np))
@@ -979,15 +1097,9 @@  static int m25p_probe(struct spi_device *spi)
 		}
 	}
 
-	flash = kzalloc(sizeof *flash, GFP_KERNEL);
+	flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
 	if (!flash)
 		return -ENOMEM;
-	flash->command = kmalloc(MAX_CMD_SIZE + (flash->fast_read ? 1 : 0),
-					GFP_KERNEL);
-	if (!flash->command) {
-		kfree(flash);
-		return -ENOMEM;
-	}
 
 	flash->spi = spi;
 	mutex_init(&flash->lock);
@@ -1015,7 +1127,6 @@  static int m25p_probe(struct spi_device *spi)
 	flash->mtd.flags = MTD_CAP_NORFLASH;
 	flash->mtd.size = info->sector_size * info->n_sectors;
 	flash->mtd._erase = m25p80_erase;
-	flash->mtd._read = m25p80_read;
 
 	/* flash protection support for STmicro chips */
 	if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
@@ -1067,6 +1178,28 @@  static int m25p_probe(struct spi_device *spi)
 
 	flash->program_opcode = OPCODE_PP;
 
+	flash->quad_read = false;
+	if (spi->mode && SPI_RX_QUAD)
+		flash->quad_read = true;
+
+	flash->command = kmalloc(MAX_CMD_SIZE + (flash->fast_read ? 1 :
+				(flash->quad_read ? 1 : 0)), GFP_KERNEL);
+	if (!flash->command) {
+		kfree(flash);
+		return -ENOMEM;
+	}
+
+	if (flash->quad_read) {
+		ret = quad_enable(flash);
+		if (ret) {
+			dev_err(&spi->dev,
+				"error enabling quad");
+			return -EINVAL;
+		}
+		flash->mtd._read = m25p80_quad_read;
+	} else
+		flash->mtd._read = m25p80_read;
+
 	if (info->addr_width)
 		flash->addr_width = info->addr_width;
 	else if (flash->mtd.size > 0x1000000) {
@@ -1074,9 +1207,9 @@  static int m25p_probe(struct spi_device *spi)
 		flash->addr_width = 4;
 		if (JEDEC_MFR(info->jedec_id) == CFI_MFR_AMD) {
 			/* Dedicated 4-byte command set */
-			flash->read_opcode = flash->fast_read ?
-				OPCODE_FAST_READ_4B :
-				OPCODE_NORM_READ_4B;
+			flash->read_opcode = (flash->fast_read ?
+				OPCODE_FAST_READ_4B : (flash->quad_read ?
+				OPCODE_QUAD_READ_4B : OPCODE_NORM_READ_4B));
 			flash->program_opcode = OPCODE_PP_4B;
 			/* No small sector erase for 4-byte command set */
 			flash->erase_opcode = OPCODE_SE_4B;