diff mbox

[v2,2/3] mtd: spi-nor: Altera ASMI Parallel II IP Core

Message ID 1505932139-2905-3-git-send-email-matthew.gerlach@linux.intel.com (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Matthew Gerlach Sept. 20, 2017, 6:28 p.m. UTC
From: Matthew Gerlach <matthew.gerlach@linux.intel.com>

This patch adds support for a spi-nor, platform driver for the
Altera ASMI Parallel II IP Core.  The intended use case is to be able
to update the flash used to load a FPGA at power up with mtd-utils.

Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
---
v2:
    minor checkpatch fixing by Wu Hao <hao.wu@intel.com>
    Use read_dummy value as suggested by Cyrille Pitchen.
    Don't assume 4 byte addressing (Cryille Pichecn and Marek Vasut).
    Fixed #define indenting as suggested by Marek Vasut.
    Added units to timer values as suggested by Marek Vasut.
    Use io(read|write)8_rep() as suggested by Marek Vasut.
    Renamed function prefixed with __ as suggested by Marek Vasut.
---
 MAINTAINERS                         |   7 +
 drivers/mtd/spi-nor/Kconfig         |   6 +
 drivers/mtd/spi-nor/Makefile        |   1 +
 drivers/mtd/spi-nor/altera-asmip2.c | 457 ++++++++++++++++++++++++++++++++++++
 include/linux/mtd/altera-asmip2.h   |  24 ++
 5 files changed, 495 insertions(+)
 create mode 100644 drivers/mtd/spi-nor/altera-asmip2.c
 create mode 100644 include/linux/mtd/altera-asmip2.h

Comments

Marek Vasut Oct. 10, 2017, 9:24 a.m. UTC | #1
On 09/20/2017 08:28 PM, matthew.gerlach@linux.intel.com wrote:
> From: Matthew Gerlach <matthew.gerlach@linux.intel.com>
> 
> This patch adds support for a spi-nor, platform driver for the
> Altera ASMI Parallel II IP Core.  The intended use case is to be able
> to update the flash used to load a FPGA at power up with mtd-utils.
> 
> Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
> ---
> v2:
>     minor checkpatch fixing by Wu Hao <hao.wu@intel.com>
>     Use read_dummy value as suggested by Cyrille Pitchen.
>     Don't assume 4 byte addressing (Cryille Pichecn and Marek Vasut).
>     Fixed #define indenting as suggested by Marek Vasut.
>     Added units to timer values as suggested by Marek Vasut.
>     Use io(read|write)8_rep() as suggested by Marek Vasut.
>     Renamed function prefixed with __ as suggested by Marek Vasut.

[...]

> +#define QSPI_ACTION_REG			0
> +#define QSPI_ACTION_RST			BIT(0)
> +#define QSPI_ACTION_EN			BIT(1)
> +#define QSPI_ACTION_SC			BIT(2)
> +#define QSPI_ACTION_CHIP_SEL_SFT	4
> +#define QSPI_ACTION_DUMMY_SFT		8
> +#define QSPI_ACTION_READ_BACK_SFT	16
> +
> +#define QSPI_FIFO_CNT_REG		4
> +#define QSPI_FIFO_DEPTH			0x200
> +#define QSPI_FIFO_CNT_MSK		0x3ff
> +#define QSPI_FIFO_CNT_RX_SFT		0
> +#define QSPI_FIFO_CNT_TX_SFT		12
> +
> +#define QSPI_DATA_REG			0x8
> +
> +#define QSPI_POLL_TIMEOUT_US		10000000

10 s poll timeout ? :)

> +#define QSPI_POLL_INTERVAL_US		5
> +
> +struct altera_asmip2 {

[...]

Otherwise looks good
Matthew Gerlach Oct. 11, 2017, 5 p.m. UTC | #2
On Tue, 10 Oct 2017, Marek Vasut wrote:

> On 09/20/2017 08:28 PM, matthew.gerlach@linux.intel.com wrote:
>> From: Matthew Gerlach <matthew.gerlach@linux.intel.com>
>>
>> This patch adds support for a spi-nor, platform driver for the
>> Altera ASMI Parallel II IP Core.  The intended use case is to be able
>> to update the flash used to load a FPGA at power up with mtd-utils.
>>
>> Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
>> ---
>> v2:
>>     minor checkpatch fixing by Wu Hao <hao.wu@intel.com>
>>     Use read_dummy value as suggested by Cyrille Pitchen.
>>     Don't assume 4 byte addressing (Cryille Pichecn and Marek Vasut).
>>     Fixed #define indenting as suggested by Marek Vasut.
>>     Added units to timer values as suggested by Marek Vasut.
>>     Use io(read|write)8_rep() as suggested by Marek Vasut.
>>     Renamed function prefixed with __ as suggested by Marek Vasut.
>
> [...]
>
>> +#define QSPI_ACTION_REG			0
>> +#define QSPI_ACTION_RST			BIT(0)
>> +#define QSPI_ACTION_EN			BIT(1)
>> +#define QSPI_ACTION_SC			BIT(2)
>> +#define QSPI_ACTION_CHIP_SEL_SFT	4
>> +#define QSPI_ACTION_DUMMY_SFT		8
>> +#define QSPI_ACTION_READ_BACK_SFT	16
>> +
>> +#define QSPI_FIFO_CNT_REG		4
>> +#define QSPI_FIFO_DEPTH			0x200
>> +#define QSPI_FIFO_CNT_MSK		0x3ff
>> +#define QSPI_FIFO_CNT_RX_SFT		0
>> +#define QSPI_FIFO_CNT_TX_SFT		12
>> +
>> +#define QSPI_DATA_REG			0x8
>> +
>> +#define QSPI_POLL_TIMEOUT_US		10000000
>
> 10 s poll timeout ? :)

Hi Marek,

The 10s timeout is fairly arbitrary.  In other words, I pulled it out of 
thin air.  Can you suggest a better timeout?  From a practical standpoint 10s 
seemed to be much better than no timeout when I was debugging bad FPGA 
images.  Without a timeout I was hanging the system when the FPGA image 
failed.  With this timeout, we get a nice message and Linux keeps running 
happily.

Thanks for the feedback,

Matthew Gerlach

>
>> +#define QSPI_POLL_INTERVAL_US		 5 >> + >> +struct 
altera_asmip2 { >
> [...]
>
> Otherwise looks good
>
> -- 
> Best regards,
> Marek Vasut
>
--
To unsubscribe from this list: send the line "unsubscribe linux-fpga" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Marek Vasut Oct. 11, 2017, 9:06 p.m. UTC | #3
On 10/11/2017 07:00 PM, matthew.gerlach@linux.intel.com wrote:
> 
> 
> On Tue, 10 Oct 2017, Marek Vasut wrote:
> 
>> On 09/20/2017 08:28 PM, matthew.gerlach@linux.intel.com wrote:
>>> From: Matthew Gerlach <matthew.gerlach@linux.intel.com>
>>>
>>> This patch adds support for a spi-nor, platform driver for the
>>> Altera ASMI Parallel II IP Core.  The intended use case is to be able
>>> to update the flash used to load a FPGA at power up with mtd-utils.
>>>
>>> Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
>>> ---
>>> v2:
>>>     minor checkpatch fixing by Wu Hao <hao.wu@intel.com>
>>>     Use read_dummy value as suggested by Cyrille Pitchen.
>>>     Don't assume 4 byte addressing (Cryille Pichecn and Marek Vasut).
>>>     Fixed #define indenting as suggested by Marek Vasut.
>>>     Added units to timer values as suggested by Marek Vasut.
>>>     Use io(read|write)8_rep() as suggested by Marek Vasut.
>>>     Renamed function prefixed with __ as suggested by Marek Vasut.
>>
>> [...]
>>
>>> +#define QSPI_ACTION_REG            0
>>> +#define QSPI_ACTION_RST            BIT(0)
>>> +#define QSPI_ACTION_EN            BIT(1)
>>> +#define QSPI_ACTION_SC            BIT(2)
>>> +#define QSPI_ACTION_CHIP_SEL_SFT    4
>>> +#define QSPI_ACTION_DUMMY_SFT        8
>>> +#define QSPI_ACTION_READ_BACK_SFT    16
>>> +
>>> +#define QSPI_FIFO_CNT_REG        4
>>> +#define QSPI_FIFO_DEPTH            0x200
>>> +#define QSPI_FIFO_CNT_MSK        0x3ff
>>> +#define QSPI_FIFO_CNT_RX_SFT        0
>>> +#define QSPI_FIFO_CNT_TX_SFT        12
>>> +
>>> +#define QSPI_DATA_REG            0x8
>>> +
>>> +#define QSPI_POLL_TIMEOUT_US        10000000
>>
>> 10 s poll timeout ? :)
> 
> Hi Marek,
> 
> The 10s timeout is fairly arbitrary.  In other words, I pulled it out of
> thin air.  Can you suggest a better timeout?  From a practical
> standpoint 10s seemed to be much better than no timeout when I was
> debugging bad FPGA images.  Without a timeout I was hanging the system
> when the FPGA image failed.  With this timeout, we get a nice message
> and Linux keeps running happily.
AFAIK the SPI subsystem has a timeout which is adaptive to the bus
clock, maybe that's what you want to use here ?
Matthew Gerlach Oct. 13, 2017, 7:24 p.m. UTC | #4
On Wed, 11 Oct 2017, Marek Vasut wrote:

> On 10/11/2017 07:00 PM, matthew.gerlach@linux.intel.com wrote:
>>
>>
>> On Tue, 10 Oct 2017, Marek Vasut wrote:
>>
>>> On 09/20/2017 08:28 PM, matthew.gerlach@linux.intel.com wrote:
>>>> From: Matthew Gerlach <matthew.gerlach@linux.intel.com>
>>>>
>>>> This patch adds support for a spi-nor, platform driver for the
>>>> Altera ASMI Parallel II IP Core.  The intended use case is to be able
>>>> to update the flash used to load a FPGA at power up with mtd-utils.
>>>>
>>>> Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
>>>> ---
>>>> v2:
>>>>     minor checkpatch fixing by Wu Hao <hao.wu@intel.com>
>>>>     Use read_dummy value as suggested by Cyrille Pitchen.
>>>>     Don't assume 4 byte addressing (Cryille Pichecn and Marek Vasut).
>>>>     Fixed #define indenting as suggested by Marek Vasut.
>>>>     Added units to timer values as suggested by Marek Vasut.
>>>>     Use io(read|write)8_rep() as suggested by Marek Vasut.
>>>>     Renamed function prefixed with __ as suggested by Marek Vasut.
>>>
>>> [...]
>>>
>>>> +#define QSPI_ACTION_REG            0
>>>> +#define QSPI_ACTION_RST            BIT(0)
>>>> +#define QSPI_ACTION_EN            BIT(1)
>>>> +#define QSPI_ACTION_SC            BIT(2)
>>>> +#define QSPI_ACTION_CHIP_SEL_SFT    4
>>>> +#define QSPI_ACTION_DUMMY_SFT        8
>>>> +#define QSPI_ACTION_READ_BACK_SFT    16
>>>> +
>>>> +#define QSPI_FIFO_CNT_REG        4
>>>> +#define QSPI_FIFO_DEPTH            0x200
>>>> +#define QSPI_FIFO_CNT_MSK        0x3ff
>>>> +#define QSPI_FIFO_CNT_RX_SFT        0
>>>> +#define QSPI_FIFO_CNT_TX_SFT        12
>>>> +
>>>> +#define QSPI_DATA_REG            0x8
>>>> +
>>>> +#define QSPI_POLL_TIMEOUT_US        10000000
>>>
>>> 10 s poll timeout ? :)
>>
>> Hi Marek,
>>
>> The 10s timeout is fairly arbitrary.  In other words, I pulled it out of
>> thin air.  Can you suggest a better timeout?  From a practical
>> standpoint 10s seemed to be much better than no timeout when I was
>> debugging bad FPGA images.  Without a timeout I was hanging the system
>> when the FPGA image failed.  With this timeout, we get a nice message
>> and Linux keeps running happily.
> AFAIK the SPI subsystem has a timeout which is adaptive to the bus
> clock, maybe that's what you want to use here ?

Hi Marek,

I looked in spi-nor.c, and I see the following two macros:
#define DEFAULT_READY_WAIT_JIFFIES		(40UL * HZ)
#define CHIP_ERASE_2MB_READY_WAIT_JIFFIES	(40UL * HZ)

These timers values are used at the spi-nor layer for waiting for an
operation to complete.  It is the time spent waiting for Work In Progress
to clear.

The timer value I'm using, QSPI_POLL_TIMEOUT_US, is used at a lower layer.
This timer value is used for the time it takes to send the opcode and tx 
data and receive any bytes from the flash.  While 10 seconds may seem 
long, I am just trying to avoid a system hang when there is a catastrophic
failure with the flash controller in the FPGA or the flash part itself.

I certainly could be missing something with regards to the timeouts, but I 
think 10s for QSPI_POLL_TIMEOUT_US doesn't hurt, and it definately helps 
when something goes wrong.

Thanks for the feedback,
Matthew Gerlach

>
> -- 
> Best regards,
> Marek Vasut
>
diff mbox

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index b87c698..af48255 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -644,6 +644,13 @@  ALPS PS/2 TOUCHPAD DRIVER
 R:	Pali Rohár <pali.rohar@gmail.com>
 F:	drivers/input/mouse/alps.*
 
+ALTERA ASMI Parallel II Driver
+M:      Matthew Gerlach <matthew.gerlach@linux.intel.com>
+L:      linux-mtd@lists.infradead.org
+S:      Maintained
+F:      drivers/mtd/spi-nor/altera-asmip2.c
+F:      inclulde/linux/mtd/altera-asmip2.h
+
 ALTERA I2C CONTROLLER DRIVER
 M:	Thor Thayer <thor.thayer@linux.intel.com>
 S:	Maintained
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 69c638d..f81dbec 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -129,4 +129,10 @@  config SPI_STM32_QUADSPI
 	  This enables support for the STM32 Quad SPI controller.
 	  We only connect the NOR to this controller.
 
+config SPI_ALTERA_ASMIP2
+        tristate "Altera ASMI Parallel II IP"
+        depends on OF && HAS_IOMEM
+        help
+          Enable support for Altera ASMI Parallel II.
+
 endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index 7d84c51..1c79324 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -1,4 +1,5 @@ 
 obj-$(CONFIG_MTD_SPI_NOR)	+= spi-nor.o
+obj-$(CONFIG_SPI_ALTERA_ASMIP2)	+= altera-asmip2.o
 obj-$(CONFIG_SPI_ASPEED_SMC)	+= aspeed-smc.o
 obj-$(CONFIG_SPI_ATMEL_QUADSPI)	+= atmel-quadspi.o
 obj-$(CONFIG_SPI_CADENCE_QUADSPI)	+= cadence-quadspi.o
diff --git a/drivers/mtd/spi-nor/altera-asmip2.c b/drivers/mtd/spi-nor/altera-asmip2.c
new file mode 100644
index 0000000..a977765
--- /dev/null
+++ b/drivers/mtd/spi-nor/altera-asmip2.c
@@ -0,0 +1,457 @@ 
+/*
+ * Copyright (C) 2017 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * 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/iopoll.h>
+#include <linux/module.h>
+#include <linux/mtd/altera-asmip2.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/of_device.h>
+
+#define QSPI_ACTION_REG			0
+#define QSPI_ACTION_RST			BIT(0)
+#define QSPI_ACTION_EN			BIT(1)
+#define QSPI_ACTION_SC			BIT(2)
+#define QSPI_ACTION_CHIP_SEL_SFT	4
+#define QSPI_ACTION_DUMMY_SFT		8
+#define QSPI_ACTION_READ_BACK_SFT	16
+
+#define QSPI_FIFO_CNT_REG		4
+#define QSPI_FIFO_DEPTH			0x200
+#define QSPI_FIFO_CNT_MSK		0x3ff
+#define QSPI_FIFO_CNT_RX_SFT		0
+#define QSPI_FIFO_CNT_TX_SFT		12
+
+#define QSPI_DATA_REG			0x8
+
+#define QSPI_POLL_TIMEOUT_US		10000000
+#define QSPI_POLL_INTERVAL_US		5
+
+struct altera_asmip2 {
+	void __iomem *csr_base;
+	u32 num_flashes;
+	struct device *dev;
+	struct altera_asmip2_flash *flash[ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP];
+	struct mutex bus_mutex;
+};
+
+struct altera_asmip2_flash {
+	struct spi_nor nor;
+	struct altera_asmip2 *q;
+};
+
+static int altera_asmip2_write_reg(struct spi_nor *nor, u8 opcode, u8 *val,
+				    int len)
+{
+	struct altera_asmip2_flash *flash = nor->priv;
+	struct altera_asmip2 *q = flash->q;
+	u32 reg;
+	int ret;
+
+	if ((len + 1) > QSPI_FIFO_DEPTH) {
+		dev_err(q->dev, "%s bad len %d > %d\n",
+			__func__, len + 1, QSPI_FIFO_DEPTH);
+		return -EINVAL;
+	}
+
+	writeb(opcode, q->csr_base + QSPI_DATA_REG);
+
+	iowrite8_rep(q->csr_base + QSPI_DATA_REG, val, len);
+
+	reg = QSPI_ACTION_EN | QSPI_ACTION_SC;
+
+	writel(reg, q->csr_base + QSPI_ACTION_REG);
+
+	ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg,
+				 (((reg >> QSPI_FIFO_CNT_TX_SFT) &
+				 QSPI_FIFO_CNT_MSK) == 0),
+				 QSPI_POLL_INTERVAL_US, QSPI_POLL_TIMEOUT_US);
+	if (ret)
+		dev_err(q->dev, "%s timed out\n", __func__);
+
+	reg = QSPI_ACTION_EN;
+
+	writel(reg, q->csr_base + QSPI_ACTION_REG);
+
+	return ret;
+}
+
+static int altera_asmip2_read_reg(struct spi_nor *nor, u8 opcode, u8 *val,
+				   int len)
+{
+	struct altera_asmip2_flash *flash = nor->priv;
+	struct altera_asmip2 *q = flash->q;
+	u32 reg;
+	int ret;
+
+	if (len > QSPI_FIFO_DEPTH) {
+		dev_err(q->dev, "%s bad len %d > %d\n",
+			__func__, len, QSPI_FIFO_DEPTH);
+		return -EINVAL;
+	}
+
+	writeb(opcode, q->csr_base + QSPI_DATA_REG);
+
+	reg = QSPI_ACTION_EN | QSPI_ACTION_SC |
+		(len << QSPI_ACTION_READ_BACK_SFT);
+
+	writel(reg, q->csr_base + QSPI_ACTION_REG);
+
+	ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg,
+				 ((reg & QSPI_FIFO_CNT_MSK) == len),
+				 QSPI_POLL_INTERVAL_US, QSPI_POLL_TIMEOUT_US);
+
+	if (!ret)
+		ioread8_rep(q->csr_base + QSPI_DATA_REG, val, len);
+	else
+		dev_err(q->dev, "%s timeout\n", __func__);
+
+	writel(QSPI_ACTION_EN, q->csr_base + QSPI_ACTION_REG);
+
+	return ret;
+}
+
+static inline void altera_asmip2_push_offset(struct altera_asmip2 *q,
+					     struct spi_nor *nor,
+					     loff_t offset)
+{
+	int i;
+	u32 val;
+
+	for (i = (nor->addr_width - 1) * 8; i >= 0; i -= 8) {
+		val = (offset & (0xff << i)) >> i;
+		writeb(val, q->csr_base + QSPI_DATA_REG);
+	}
+}
+
+static ssize_t altera_asmip2_read(struct spi_nor *nor, loff_t from, size_t len,
+				   u_char *buf)
+{
+	struct altera_asmip2_flash *flash = nor->priv;
+	struct altera_asmip2 *q = flash->q;
+	size_t bytes_to_read;
+	u32 reg;
+	int ret;
+
+	bytes_to_read = min_t(size_t, len, QSPI_FIFO_DEPTH);
+
+	writeb(nor->read_opcode, q->csr_base + QSPI_DATA_REG);
+
+	altera_asmip2_push_offset(q, nor, from);
+
+	reg = QSPI_ACTION_EN | QSPI_ACTION_SC |
+		(nor->read_dummy << QSPI_ACTION_DUMMY_SFT) |
+		(bytes_to_read << QSPI_ACTION_READ_BACK_SFT);
+
+	writel(reg, q->csr_base + QSPI_ACTION_REG);
+
+	ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg,
+				 ((reg & QSPI_FIFO_CNT_MSK) ==
+				 bytes_to_read), QSPI_POLL_INTERVAL_US,
+				 QSPI_POLL_TIMEOUT_US);
+	if (ret) {
+		dev_err(q->dev, "%s timed out\n", __func__);
+		bytes_to_read = 0;
+	} else
+		ioread8_rep(q->csr_base + QSPI_DATA_REG, buf, bytes_to_read);
+
+	writel(QSPI_ACTION_EN, q->csr_base + QSPI_ACTION_REG);
+
+	return bytes_to_read;
+}
+
+static ssize_t altera_asmip2_write(struct spi_nor *nor, loff_t to,
+				    size_t len, const u_char *buf)
+{
+	struct altera_asmip2_flash *flash = nor->priv;
+	struct altera_asmip2 *q = flash->q;
+	size_t bytes_to_write;
+	u32 reg;
+	int ret;
+
+	bytes_to_write = min_t(size_t, len,
+			       (QSPI_FIFO_DEPTH - (nor->addr_width + 1)));
+
+	writeb(nor->program_opcode, q->csr_base + QSPI_DATA_REG);
+
+	altera_asmip2_push_offset(q, nor, to);
+
+	iowrite8_rep(q->csr_base + QSPI_DATA_REG, buf, bytes_to_write);
+
+	reg = QSPI_ACTION_EN | QSPI_ACTION_SC;
+
+	writel(reg, q->csr_base + QSPI_ACTION_REG);
+
+	ret = readl_poll_timeout(q->csr_base + QSPI_FIFO_CNT_REG, reg,
+				 (((reg >> QSPI_FIFO_CNT_TX_SFT) &
+				 QSPI_FIFO_CNT_MSK) == 0),
+				 QSPI_POLL_INTERVAL_US, QSPI_POLL_TIMEOUT_US);
+
+	if (ret) {
+		dev_err(q->dev,
+			"%s timed out waiting for fifo to clear\n",
+			__func__);
+		bytes_to_write = 0;
+	}
+
+	writel(QSPI_ACTION_EN, q->csr_base + QSPI_ACTION_REG);
+
+	return bytes_to_write;
+}
+
+static int altera_asmip2_prep(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+	struct altera_asmip2_flash *flash = nor->priv;
+	struct altera_asmip2 *q = flash->q;
+
+	mutex_lock(&q->bus_mutex);
+
+	return 0;
+}
+
+static void altera_asmip2_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+	struct altera_asmip2_flash *flash = nor->priv;
+	struct altera_asmip2 *q = flash->q;
+
+	mutex_unlock(&q->bus_mutex);
+}
+
+static int altera_asmip2_setup_banks(struct device *dev,
+				      u32 bank, struct device_node *np)
+{
+	const struct spi_nor_hwcaps hwcaps = {
+		.mask = SNOR_HWCAPS_READ |
+			SNOR_HWCAPS_READ_FAST |
+			SNOR_HWCAPS_PP,
+	};
+	struct altera_asmip2 *q = dev_get_drvdata(dev);
+	struct altera_asmip2_flash *flash;
+	struct spi_nor *nor;
+	int ret = 0;
+
+	if (bank > q->num_flashes - 1)
+		return -EINVAL;
+
+	flash = devm_kzalloc(q->dev, sizeof(*flash), GFP_KERNEL);
+	if (!flash)
+		return -ENOMEM;
+
+	q->flash[bank] = flash;
+	flash->q = q;
+
+	nor = &flash->nor;
+	nor->dev = dev;
+	nor->priv = flash;
+	nor->mtd.priv = nor;
+	spi_nor_set_flash_node(nor, np);
+
+	/* spi nor framework*/
+	nor->read_reg = altera_asmip2_read_reg;
+	nor->write_reg = altera_asmip2_write_reg;
+	nor->read = altera_asmip2_read;
+	nor->write = altera_asmip2_write;
+	nor->prepare = altera_asmip2_prep;
+	nor->unprepare = altera_asmip2_unprep;
+
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
+	if (ret) {
+		dev_err(nor->dev, "flash not found\n");
+		return ret;
+	}
+
+	ret =  mtd_device_register(&nor->mtd, NULL, 0);
+
+	return ret;
+}
+
+static int altera_asmip2_create(struct device *dev, void __iomem *csr_base)
+{
+	struct altera_asmip2 *q;
+	u32 reg;
+
+	q = devm_kzalloc(dev, sizeof(*q), GFP_KERNEL);
+	if (!q)
+		return -ENOMEM;
+
+	q->dev = dev;
+	q->csr_base = csr_base;
+
+	mutex_init(&q->bus_mutex);
+
+	dev_set_drvdata(dev, q);
+
+	reg = readl(q->csr_base + QSPI_ACTION_REG);
+	if (!(reg & QSPI_ACTION_RST)) {
+		writel((reg | QSPI_ACTION_RST), q->csr_base + QSPI_ACTION_REG);
+		dev_info(dev, "%s asserting reset\n", __func__);
+		udelay(10);
+	}
+
+	writel((reg & ~QSPI_ACTION_RST), q->csr_base + QSPI_ACTION_REG);
+	udelay(10);
+
+	return 0;
+}
+
+static int altera_asmip2_add_bank(struct device *dev,
+			 u32 bank, struct device_node *np)
+{
+	struct altera_asmip2 *q = dev_get_drvdata(dev);
+
+	if (q->num_flashes >= ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP)
+		return -ENOMEM;
+
+	q->num_flashes++;
+
+	return altera_asmip2_setup_banks(dev, bank, np);
+}
+
+static int altera_asmip2_remove_banks(struct device *dev)
+{
+	struct altera_asmip2 *q = dev_get_drvdata(dev);
+	struct altera_asmip2_flash *flash;
+	int i;
+	int ret = 0;
+
+	if (!q)
+		return -EINVAL;
+
+	/* clean up for all nor flash */
+	for (i = 0; i < q->num_flashes; i++) {
+		flash = q->flash[i];
+		if (!flash)
+			continue;
+
+		/* clean up mtd stuff */
+		ret = mtd_device_unregister(&flash->nor.mtd);
+		if (ret) {
+			dev_err(dev, "error removing mtd\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int altera_asmip2_probe_with_pdata(struct platform_device *pdev,
+			     struct altera_asmip2_plat_data *qdata)
+{
+	struct device *dev = &pdev->dev;
+	int ret, i;
+
+	ret = altera_asmip2_create(dev, qdata->csr_base);
+
+	if (ret) {
+		dev_err(dev, "failed to create qspi device %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < qdata->num_chip_sel; i++) {
+		ret = altera_asmip2_add_bank(dev, i, NULL);
+		if (ret) {
+			dev_err(dev, "failed to add qspi bank %d\n", ret);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static int altera_asmip2_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	struct altera_asmip2_plat_data *qdata;
+	struct resource *res;
+	void __iomem *csr_base;
+	u32 bank;
+	int ret;
+	struct device_node *pp;
+
+	qdata = dev_get_platdata(dev);
+
+	if (qdata)
+		return altera_asmip2_probe_with_pdata(pdev, qdata);
+
+	if (!np) {
+		dev_err(dev, "no device tree found %p\n", pdev);
+		return -ENODEV;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	csr_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(csr_base)) {
+		dev_err(dev, "%s: ERROR: failed to map csr base\n", __func__);
+		return PTR_ERR(csr_base);
+	}
+
+	ret = altera_asmip2_create(dev, csr_base);
+
+	if (ret) {
+		dev_err(dev, "failed to create qspi device\n");
+		return ret;
+	}
+
+	for_each_available_child_of_node(np, pp) {
+		of_property_read_u32(pp, "reg", &bank);
+		if (bank >= ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP) {
+			dev_err(dev, "bad reg value %u >= %u\n", bank,
+				ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP);
+			goto error;
+		}
+
+		if (altera_asmip2_add_bank(dev, bank, pp)) {
+			dev_err(dev, "failed to add bank %u\n", bank);
+			goto error;
+		}
+	}
+
+	return 0;
+error:
+	altera_asmip2_remove_banks(dev);
+	return -EIO;
+}
+
+static int altera_asmip2_remove(struct platform_device *pdev)
+{
+	struct altera_asmip2 *q = dev_get_drvdata(&pdev->dev);
+
+	mutex_destroy(&q->bus_mutex);
+
+	return altera_asmip2_remove_banks(&pdev->dev);
+}
+
+static const struct of_device_id altera_asmip2_id_table[] = {
+	{ .compatible = "altr,asmi-parallel2-spi-nor",},
+	{}
+};
+MODULE_DEVICE_TABLE(of, altera_asmip2_id_table);
+
+static struct platform_driver altera_asmip2_driver = {
+	.driver = {
+		.name = ALTERA_ASMIP2_DRV_NAME,
+		.of_match_table = altera_asmip2_id_table,
+	},
+	.probe = altera_asmip2_probe,
+	.remove = altera_asmip2_remove,
+};
+module_platform_driver(altera_asmip2_driver);
+
+MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>");
+MODULE_DESCRIPTION("Altera ASMI Parallel II");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" ALTERA_ASMIP2_DRV_NAME);
diff --git a/include/linux/mtd/altera-asmip2.h b/include/linux/mtd/altera-asmip2.h
new file mode 100644
index 0000000..580c43c
--- /dev/null
+++ b/include/linux/mtd/altera-asmip2.h
@@ -0,0 +1,24 @@ 
+/*
+ *
+ * Copyright 2017 Intel Corporation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef __ALTERA_QUADSPI_H
+#define __ALTERA_QUADSPI_H
+
+#include <linux/device.h>
+
+#define ALTERA_ASMIP2_DRV_NAME "altr-asmip2"
+#define ALTERA_ASMIP2_MAX_NUM_FLASH_CHIP 3
+#define ALTERA_ASMIP2_RESOURCE_SIZE 0x10
+
+struct altera_asmip2_plat_data {
+	void __iomem *csr_base;
+	u32 num_chip_sel;
+};
+
+#endif