diff mbox

[mtd] mtd:devices: Add Altera EPCQ Driver

Message ID A8F913A698B3864C898FDDF9BF921BC9E2F20E@PG-ITEXCH01.altera.priv.altera.com
State Superseded
Headers show

Commit Message

vndao@altera.com Jan. 13, 2015, 1:21 a.m. UTC
Hi Linux Community,
It is been a while since I submitted this patch. Could you please help me to review this driver?
Thanks and Regards,
Viet Nga Dao

-----Original Message-----
From: Viet Nga Dao
Sent: Thursday, December 18, 2014 4:23 PM
To: dwmw2@infradead.org; computersforpeace@gmail.com
Cc: linux-mtd@lists.infradead.org; linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; ngachi86@gmail.com; Viet Nga Dao
Subject: [PATCH mtd] mtd:devices: Add Altera EPCQ Driver

From: Viet Nga Dao <vndao@altera.com>

Altera EPCQ Controller is a soft IP which enables access to Altera EPCQ and EPCS flash chips. This patch adds driver for these devices.

Signed-off-by: Viet Nga Dao <vndao@altera.com>
---
 .../devicetree/bindings/mtd/altera_epcq.txt        |   45 ++
 drivers/mtd/devices/Kconfig                        |   12 +
 drivers/mtd/devices/Makefile                       |    2 +-
 drivers/mtd/devices/altera_epcq.c                  |  804 ++++++++++++++++++++
 drivers/mtd/devices/altera_epcq.h                  |  130 ++++
 5 files changed, 992 insertions(+), 1 deletions(-)  create mode 100644 Documentation/devicetree/bindings/mtd/altera_epcq.txt
 create mode 100644 drivers/mtd/devices/altera_epcq.c  create mode 100644 drivers/mtd/devices/altera_epcq.h

+ * 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/>.
+ */
+
+#ifndef __ALTERA_ECPQ_H
+#define __ALTERA_ECPQ_H
+
+#define ALTERA_EPCQ_RESOURCE_NAME        "altera_epcq"
+/* max possible slots for serial flash chip in the EPCQ controller */
+#define MAX_NUM_FLASH_CHIP     3
+
+/* macro to define partitions for flash devices */
+#define DEFINE_PARTS(n, of, s)         \
+{                                      \
+       .name = n,                      \
+       .offset = of,                   \
+       .size = s,                      \
+}
+
+struct altera_epcq_flash_info {
+       u32 bank;
+       struct mtd_partition *partitions;
+       int nr_partitions;
+};
+
+struct altera_epcq_plat_data {
+       void __iomem *csr_base;
+       void __iomem *data_base;
+       bool is_epcs;
+       u32 num_flashes;
+       struct altera_epcq_flash_info *board_flash_info;
+       struct device_node *np[MAX_NUM_FLASH_CHIP]; };
+
+struct altera_epcq {
+       struct clk *clk;
+       bool is_epcs;
+       struct mutex lock;      /*device lock*/
+       void __iomem *csr_base;
+       void __iomem *data_base;
+       struct platform_device *pdev;
+       u32 num_flashes;
+       struct altera_epcq_flash *flash[MAX_NUM_FLASH_CHIP]; };
+
+struct altera_epcq_flash {
+       u32 bank;
+       u32 num_sectors;
+       u32 num_parts;
+       struct mtd_partition *parts;
+       struct mutex lock;      /*flash lock*/
+       struct mtd_info mtd;
+};
+
+/* Define max times to check status register before we give up. */
+#define EPCQ_MAX_TIME_OUT                      (40 * HZ)
+
+/* defines for status register */
+#define EPCQ_SR_REG                            0x0
+#define EPCQ_SR_WIP_MASK                       0x00000001
+#define EPCQ_SR_WIP                            0x1
+#define EPCQ_SR_WEL                            0x2
+#define EPCQ_SR_BP0                            0x4
+#define EPCQ_SR_BP1                            0x8
+#define EPCQ_SR_BP2                            0x10
+#define EPCQ_SR_BP3                            0x40
+#define EPCQ_SR_TB                             0x20
+
+/* defines for device id register */
+#define EPCQ_SID_REG                           0x4
+#define EPCQ_RDID_REG                          0x8
+#define EPCQ_RDID_MASK                         0x000000FF
+/*
+ * EPCQ_MEM_OP register offset
+ *
+ * The EPCQ_MEM_OP register is used to do memory protect and erase
+operations
+ *
+ */
+#define EPCQ_MEM_OP_REG                                0xC
+
+#define EPCQ_MEM_OP_CMD_MASK                   0x00000003
+#define EPCQ_MEM_OP_BULK_ERASE_CMD             0x00000001
+#define EPCQ_MEM_OP_SECTOR_ERASE_CMD           0x00000002
+#define EPCQ_MEM_OP_SECTOR_PROTECT_CMD         0x00000003
+#define EPCQ_MEM_OP_SECTOR_VALUE_MASK          0x0003FF00
+#define EPCQ_MEM_OP_SECTOR_PROTECT_VALUE_MASK  0x00001F00
+#define EPCQ_MEM_OP_SECTOR_PROTECT_SHIFT       8
+/*
+ * EPCQ_ISR register offset
+ *
+ * The EPCQ_ISR register is used to determine whether an invalid write
+or erase
+ * operation trigerred an interrupt
+ *
+ */
+#define EPCQ_ISR_REG                           0x10
+
+#define EPCQ_ISR_ILLEGAL_ERASE_MASK            0x00000001
+#define EPCQ_ISR_ILLEGAL_WRITE_MASK            0x00000002
+
+/*
+ * EPCQ_IMR register offset
+ *
+ * The EPCQ_IMR register is used to mask the invalid erase or the
+invalid write
+ * interrupts.
+ *
+ */
+#define EPCQ_IMR_REG                           0x14
+#define EPCQ_IMR_ILLEGAL_ERASE_MASK            0x00000001
+
+#define EPCQ_IMR_ILLEGAL_WRITE_MASK            0x00000002
+
+#define EPCQ_CHIP_SELECT_REG                   0x18
+#define EPCQ_CHIP_SELECT_MASK                  0x00000007
+#define EPCQ_CHIP_SELECT_0                     0x00000001
+#define EPCQ_CHIP_SELECT_1                     0x00000002
+#define EPCQ_CHIP_SELECT_2                     0x00000004
+
+#endif /* __ALTERA_ECPQ_H */
--
1.7.7.4
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/mtd/altera_epcq.txt b/Documentation/devicetree/bindings/mtd/altera_epcq.txt
new file mode 100644
index 0000000..d14f50e
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/altera_epcq.txt
@@ -0,0 +1,45 @@ 
+* MTD Altera EPCQ driver
+
+Required properties:
+- compatible: Should be "altr,epcq-1.0"
+- reg: Address and length of the register set  for the device. It
+contains
+  the information of registers in the same order as described by
+reg-names
+- reg-names: Should contain the reg names
+  "csr_base": Should contain the register configuration base address
+  "data_base": Should contain the data base address
+- is-epcs: boolean type.
+               If present, the device contains EPCS flashes.
+               Otherwise, it contains EPCQ flashes.
+- #address-cells: Must be <1>.
+- #size-cells: Must be <0>.
+- flash device tree subnode, there must be a node with the following fields:
+       - reg: Should contain the flash id
+       - #address-cells: please refer to /mtd/partition.txt
+       - #size-cells: please refer to /mtd/partition.txt
+       For partitions inside each flash, please refer to /mtd/partition.txt
+
+Example:
+
+                       epcq_controller_0: epcq@0x000000000 {
+                               compatible = "altr,epcq-1.0";
+                               reg = <0x00000001 0x00000000 0x00000020>,
+                                       <0x00000000 0x00000000 0x02000000>;
+                               reg-names = "csr_base", "data_base";
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+                               flash0: epcq256@0 {
+                                       reg = <0>;
+                                       #address-cells = <1>;
+                                       #size-cells = <1>;
+                                       partition@0 {
+                                               /* 16 MB for raw data. */
+                                               label = "EPCQ Flash 0 raw data";
+                                               reg = <0x0 0x1000000>;
+                                       };
+                                       partition@1000000 {
+                                               /* 16 MB for jffs2 data. */
+                                               label = "EPCQ Flash 0 JFFS 2";
+                                               reg = <0x1000000 0x1000000>;
+                                       };
+                               };
+                       }; //end epcq@0x000000000 (epcq_controller_0)
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index c49d0b1..020b864 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -218,6 +218,18 @@  config MTD_ST_SPI_FSM
          SPI Fast Sequence Mode (FSM) Serial Flash Controller and support
          for a subset of connected Serial Flash devices.

+config MTD_ALTERA_EPCQ
+       tristate "Support Altera EPCQ/EPCS Flash chips"
+       depends on OF
+       help
+         This enables access to Altera EPCQ/EPCS flash chips, used for data
+         storage. See the driver source for the current list,
+         or to add other chips.
+
+         If you want to compile this driver as a module ( = code which can be
+         inserted in and removed from the running kernel whenever you want),
+         say M here and read <file:Documentation/kbuild/modules.txt>.
+
 if MTD_DOCG3
 config BCH_CONST_M
        default 14
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index f0b0e61..b429c4d 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -16,6 +16,6 @@  obj-$(CONFIG_MTD_SPEAR_SMI)   += spear_smi.o
 obj-$(CONFIG_MTD_SST25L)       += sst25l.o
 obj-$(CONFIG_MTD_BCM47XXSFLASH)        += bcm47xxsflash.o
 obj-$(CONFIG_MTD_ST_SPI_FSM)    += st_spi_fsm.o
-
+obj-$(CONFIG_MTD_ALTERA_EPCQ)          += altera_epcq.o

 CFLAGS_docg3.o                 += -I$(src)
diff --git a/drivers/mtd/devices/altera_epcq.c b/drivers/mtd/devices/altera_epcq.c
new file mode 100644
index 0000000..09213d5
--- /dev/null
+++ b/drivers/mtd/devices/altera_epcq.c
@@ -0,0 +1,804 @@ 
+/*
+ * Copyright (C) 2014 Altera 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/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/param.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "altera_epcq.h"
+
+/* data structure to maintain flash ids from different vendors */
+struct flash_device {
+       char *name;
+       bool is_epcs;
+       u32 device_id;
+       uint32_t sectorsize_inbytes;
+       uint64_t size_inbytes;
+       u32 pagesize;
+};
+
+#define FLASH_ID(_n, _is_epcs, _id, _ssize, _size, _psize)     \
+{                              \
+       .name = (_n),           \
+       .is_epcs = (_is_epcs),          \
+       .device_id = (_id),     \
+       .sectorsize_inbytes = (_ssize), \
+       .size_inbytes = (_size),        \
+       .pagesize = (_psize),   \
+}
+
+static struct flash_device flash_devices[] = {
+       FLASH_ID("epcq16"    , 0, 0x15, 0x10000, 0x200000, 0x100),
+       FLASH_ID("epcq32"    , 0, 0x16, 0x10000, 0x400000, 0x100),
+       FLASH_ID("epcq64"    , 0, 0x17, 0x10000, 0x800000, 0x100),
+       FLASH_ID("epcq128"   , 0, 0x18, 0x10000, 0x1000000, 0x100),
+       FLASH_ID("epcq256"   , 0, 0x19, 0x10000, 0x2000000, 0x100),
+       FLASH_ID("epcq512"   , 0, 0x20, 0x10000, 0x4000000, 0x100),
+
+       FLASH_ID("epcs16"    , 1, 0x14, 0x10000, 0x200000, 0x100),
+       FLASH_ID("epcs64"    , 1, 0x16, 0x10000, 0x800000, 0x100),
+       FLASH_ID("epcs128"   , 1, 0x18, 0x40000, 0x1000000, 0x100),
+};
+
+static inline struct altera_epcq_flash *get_flash_data(struct mtd_info
+*mtd) {
+       return container_of(mtd, struct altera_epcq_flash, mtd); }
+
+static u32 altera_epcq_read_sr(struct altera_epcq *dev) {
+       return readl(dev->csr_base + EPCQ_SR_REG); }
+
+static int altera_epcq_wait_till_ready(struct altera_epcq *dev) {
+       unsigned long finish;
+       int status;
+
+       finish = jiffies + EPCQ_MAX_TIME_OUT;
+       do {
+               status = altera_epcq_read_sr(dev);
+               if (status < 0)
+                       return status;
+               else if (!(status & EPCQ_SR_WIP))
+                       return 0;
+
+               cond_resched();
+       } while (!time_after_eq(jiffies, finish));
+
+       dev_err(&dev->pdev->dev, "epcq controller is busy, timeout\n");
+       return -EBUSY;
+}
+
+static int get_flash_index(u32 flash_id, bool is_epcs) {
+       int index;
+
+       for (index = 0; index < ARRAY_SIZE(flash_devices); index++) {
+               if (flash_devices[index].device_id == flash_id &&
+                   flash_devices[index].is_epcs == is_epcs)
+                       return index;
+       }
+
+       /* Memory chip is not listed and not supported */
+       return -ENODEV;
+}
+
+static int altera_epcq_write_erase_check(struct altera_epcq *dev,
+                                        bool write_erase)
+{
+       u32 val;
+       u32 mask;
+
+       if (write_erase)
+               mask = EPCQ_ISR_ILLEGAL_WRITE_MASK;
+       else
+               mask = EPCQ_ISR_ILLEGAL_ERASE_MASK;
+
+       val = readl(dev->csr_base + EPCQ_ISR_REG);
+       if (val & mask) {
+               dev_err(&dev->pdev->dev,
+                       "write/erase failed, sector might be protected\n");
+               /*clear this status for next use*/
+               writel(val, dev->csr_base + EPCQ_ISR_REG);
+               return -EIO;
+       }
+       return 0;
+}
+
+static int altera_epcq_erase_chip(struct mtd_info *mtd) {
+       int ret;
+       u32 val;
+       struct altera_epcq *dev = mtd->priv;
+
+       /* Wait until finished previous write command. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               return ret;
+
+       /* erase chip. */
+       val = EPCQ_MEM_OP_BULK_ERASE_CMD;
+       writel(val, dev->csr_base + EPCQ_MEM_OP_REG);
+
+       /* Wait until finished previous write command. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               return ret;
+
+        /* check whether write triggered a illegal write interrupt */
+       ret = altera_epcq_write_erase_check(dev, 0);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int altera_epcq_addr_to_sector(struct altera_epcq_flash *flash,
+                                     int addr_offset)
+{
+       int sector = 0;
+       int i;
+
+       for (i = 0; i < flash->num_sectors; i++) {
+               if (addr_offset >= i * flash->mtd.erasesize && addr_offset <
+                   (i * flash->mtd.erasesize + flash->mtd.erasesize)) {
+                       sector = i;
+                       return sector;
+               }
+       }
+       return -1;
+}
+
+static int altera_epcq_erase_sector(struct mtd_info *mtd,
+                                   int addr_offset)
+{
+       struct altera_epcq_flash *flash = get_flash_data(mtd);
+       struct altera_epcq *dev = mtd->priv;
+       u32 val;
+       int ret;
+       int sector_value;
+
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               return ret;
+
+       sector_value = altera_epcq_addr_to_sector(flash, addr_offset);
+
+       /* sanity check that block_offset is a valid sector number */
+       if (sector_value < 0)
+               return -EINVAL;
+
+       /* sector value should occupy bits 17:8 */
+       val = (sector_value << 8) & EPCQ_MEM_OP_SECTOR_VALUE_MASK;
+
+       /* sector erase commands occupies lower 2 bits */
+       val |= EPCQ_MEM_OP_SECTOR_ERASE_CMD;
+
+       /* write sector erase command to EPCQ_MEM_OP register*/
+       writel(val, dev->csr_base + EPCQ_MEM_OP_REG);
+
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               return ret;
+
+        /* check whether write triggered a illegal write interrupt */
+       ret = altera_epcq_write_erase_check(dev, 0);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static int altera_epcq_erase(struct mtd_info *mtd, struct erase_info
+*e_info) {
+       u32 addr;
+       int ret;
+       u32 len;
+       struct altera_epcq_flash *flash = get_flash_data(mtd);
+       struct altera_epcq *dev = mtd->priv;
+
+       addr = e_info->addr;
+       len = e_info->len;
+
+       if (flash->bank > dev->num_flashes - 1) {
+               dev_err(&dev->pdev->dev, "Invalid chip id\n");
+               return -EINVAL;
+       }
+       mutex_lock(&flash->lock);
+
+       /*erase whole chip*/
+       if (len == mtd->size) {
+               if (altera_epcq_erase_chip(mtd)) {
+                       e_info->state = MTD_ERASE_FAILED;
+                       mutex_unlock(&flash->lock);
+                       return -EIO;
+               }
+       /*"sector"-at-a-time erase*/
+       } else {
+               while (len) {
+                       ret = altera_epcq_erase_sector(mtd, addr);
+                       if (ret) {
+                               e_info->state = MTD_ERASE_FAILED;
+                               mutex_unlock(&flash->lock);
+                               return -EIO;
+                       }
+                       addr += mtd->erasesize;
+                       if (len < mtd->erasesize)
+                               len = 0;
+                       else
+                               len -= mtd->erasesize;
+               }
+       }
+       mutex_unlock(&flash->lock);
+       e_info->state = MTD_ERASE_DONE;
+       mtd_erase_callback(e_info);
+
+       return 0;
+}
+
+static int altera_epcq_read(struct mtd_info *mtd, loff_t from, size_t len,
+                           size_t *retlen, u8 *buf)
+{
+       struct altera_epcq_flash *flash = get_flash_data(mtd);
+       struct altera_epcq *dev = mtd->priv;
+       void *src;
+       int ret = 0;
+
+       if (!flash || !dev)
+               return -ENODEV;
+
+       if (flash->bank > dev->num_flashes - 1) {
+               dev_err(&dev->pdev->dev, "Invalid chip id\n");
+               return -EINVAL;
+       }
+
+       src = dev->data_base + from;
+
+       mutex_lock(&flash->lock);
+       /* wait till previous write/erase is done. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               goto err;
+
+       memcpy_fromio(buf, (u8 *)src, len);
+       *retlen = len;
+
+err:
+       mutex_unlock(&flash->lock);
+       return ret;
+}
+
+static int altera_epcq_write(struct mtd_info *mtd, loff_t to, size_t len,
+                            size_t *retlen, const u8 *buf)
+{
+       struct altera_epcq_flash *flash = get_flash_data(mtd);
+       struct altera_epcq *dev = mtd->priv;
+       void *dest;
+       int ret = 0;
+
+       if (!flash || !dev)
+               return -ENODEV;
+
+       if (flash->bank > dev->num_flashes - 1) {
+               dev_err(&dev->pdev->dev, "Invalid chip id\n");
+               return -EINVAL;
+       }
+       dest = dev->data_base + to;
+
+       mutex_lock(&flash->lock);
+
+       /* wait until finished previous write command. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               goto err;
+
+       memcpy_toio(dest, buf, len);
+       *retlen += len;
+
+       /* wait until finished previous write command. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               goto err;
+
+        /* check whether write triggered a illegal write interrupt */
+       ret = altera_epcq_write_erase_check(dev, 1);
+       if (ret < 0)
+               goto err;
+
+err:
+       mutex_unlock(&flash->lock);
+       return ret;
+}
+
+static int altera_epcq_lock(struct mtd_info *mtd, loff_t ofs, uint64_t
+len) {
+       struct altera_epcq_flash *flash = get_flash_data(mtd);
+       struct altera_epcq *dev = mtd->priv;
+       uint32_t offset = ofs;
+       int ret = 0;
+       u32 sector_start, sector_end;
+       u32 num_sectors;
+       u32 mem_op;
+       unsigned sr_bp = 0;
+       unsigned sr_tb = 0;
+
+       sector_start = altera_epcq_addr_to_sector(flash, offset);
+       sector_end = altera_epcq_addr_to_sector(flash, offset + len);
+       num_sectors = flash->num_sectors;
+       dev_dbg(&dev->pdev->dev,
+               "%s: num_setor is %u,sector start is %u,sector end is %u\n",
+               __func__, num_sectors, sector_start, sector_end);
+
+       mutex_lock(&flash->lock);
+       /* wait until finished previous write command. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               goto err;
+
+       if (sector_start >= num_sectors/2) {
+               if (sector_start < num_sectors-(num_sectors / 4))
+                       sr_bp = __ilog2_u32(num_sectors);
+               else if (sector_start < num_sectors-(num_sectors / 8))
+                       sr_bp = __ilog2_u32(num_sectors) - 1;
+               else if (sector_start < num_sectors-(num_sectors / 16))
+                       sr_bp = __ilog2_u32(num_sectors) - 2;
+               else if (sector_start < num_sectors-(num_sectors / 32))
+                       sr_bp = __ilog2_u32(num_sectors) - 3;
+               else if (sector_start < num_sectors-(num_sectors / 64))
+                       sr_bp = __ilog2_u32(num_sectors) - 4;
+               else if (sector_start < num_sectors-(num_sectors / 128))
+                       sr_bp = __ilog2_u32(num_sectors) - 5;
+               else if (sector_start < num_sectors-(num_sectors / 256))
+                       sr_bp = __ilog2_u32(num_sectors) - 6;
+               else if (sector_start < num_sectors-(num_sectors / 512))
+                       sr_bp = __ilog2_u32(num_sectors) - 7;
+               else if (sector_start < num_sectors-(num_sectors / 1024))
+                       sr_bp = __ilog2_u32(num_sectors) - 8;
+               else
+                       sr_bp = 0;  /* non area protected */
+
+               if (sr_bp < 0) {
+                       dev_err(&dev->pdev->dev, "%s: address is out of range\n"
+                               , __func__);
+                       ret = -EINVAL;
+                       goto err;
+               }
+               /*set TB = 0*/
+               sr_tb = 0;
+
+       } else {
+               if (sector_end < 1)
+                       sr_bp = 1;
+               else if (sector_end < 2)
+                       sr_bp = 2;
+               else if (sector_end < 4)
+                       sr_bp = 3;
+               else if (sector_end < 8)
+                       sr_bp = 4;
+               else if (sector_end < 16)
+                       sr_bp = 5;
+               else if (sector_end < 32)
+                       sr_bp = 6;
+               else if (sector_end < 64)
+                       sr_bp = 7;
+               else if (sector_end < 128)
+                       sr_bp = 8;
+               else if (sector_end < 256)
+                       sr_bp = 9;
+               else if (sector_end < 512)
+                       sr_bp = 10;
+               else
+                       sr_bp = 16; /*protect all areas*/
+
+               sr_tb = 1;
+       }
+
+       mem_op = (sr_tb << 12) | (sr_bp << 8);
+       mem_op &= EPCQ_MEM_OP_SECTOR_PROTECT_VALUE_MASK;
+       mem_op |= EPCQ_MEM_OP_SECTOR_PROTECT_CMD;
+       writel(mem_op, dev->csr_base + EPCQ_MEM_OP_REG);
+
+err:
+       mutex_unlock(&flash->lock);
+       return ret;
+}
+
+static int altera_epcq_unlock(struct mtd_info *mtd, loff_t ofs,
+uint64_t len) {
+       struct altera_epcq_flash *flash = get_flash_data(mtd);
+       struct altera_epcq *dev = mtd->priv;
+
+       int ret = 0;
+       u32 mem_op;
+
+       mutex_lock(&flash->lock);
+       /* wait until finished previous write command. */
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               goto err;
+       dev_info(&dev->pdev->dev, "Unlock all protected area\n");
+       mem_op = 0;
+       mem_op &= EPCQ_MEM_OP_SECTOR_PROTECT_VALUE_MASK;
+       mem_op |= EPCQ_MEM_OP_SECTOR_PROTECT_CMD;
+       writel(mem_op, dev->csr_base + EPCQ_MEM_OP_REG);
+
+err:
+       mutex_unlock(&flash->lock);
+       return ret;
+}
+
+static void altera_epcq_chip_select(struct altera_epcq *dev, u32 bank)
+{
+       u32 val = 0;
+
+       switch (bank) {
+       case 0:
+               val = EPCQ_CHIP_SELECT_0;
+               break;
+       case 1:
+               val = EPCQ_CHIP_SELECT_1;
+               break;
+       case 2:
+               val = EPCQ_CHIP_SELECT_2;
+               break;
+       default:
+               return;
+       }
+
+       writel(val, dev->csr_base + EPCQ_CHIP_SELECT_REG); }
+
+static int altera_epcq_probe_flash(struct altera_epcq *dev, u32 bank) {
+       int ret = 0;
+       u32 val = 0;
+
+       mutex_lock(&dev->lock);
+
+       /*select bank*/
+       altera_epcq_chip_select(dev, bank);
+
+       ret = altera_epcq_wait_till_ready(dev);
+       if (ret)
+               goto err;
+
+       /* get device sillicon id */
+       if (dev->is_epcs)
+               val = readl(dev->csr_base + EPCQ_SID_REG);
+       else
+               val = readl(dev->csr_base + EPCQ_RDID_REG);
+
+       /* get flash index based on the device list*/
+       ret = get_flash_index(val, dev->is_epcs);
+       return 0;
+err:
+       mutex_unlock(&dev->lock);
+       return ret;
+}
+
+static int altera_epcq_probe_config_dt(struct platform_device *pdev,
+                                      struct device_node *np,
+                                      struct altera_epcq_plat_data *pdata) {
+       struct device_node *pp = NULL;
+       struct resource *epcq_res;
+       int i = 0;
+       u32 id;
+
+       pdata->is_epcs = of_property_read_bool(np, "is-epcs");
+
+       epcq_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+                                               "csr_base");
+       if (!epcq_res) {
+               dev_err(&pdev->dev, "resource csr base is not defined\n");
+               return -ENODEV;
+       }
+
+       pdata->csr_base = devm_ioremap_resource(&pdev->dev, epcq_res);
+       if (IS_ERR(pdata->csr_base)) {
+               dev_err(&pdev->dev, "%s: ERROR: failed to map csr base\n",
+                       __func__);
+               return PTR_ERR(pdata->csr_base);
+       }
+
+       epcq_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+                                               "data_base");
+       if (!epcq_res) {
+               dev_err(&pdev->dev, "resource data base is not defined\n");
+               return -ENODEV;
+       }
+
+       pdata->data_base = devm_ioremap_resource(&pdev->dev, epcq_res);
+       if (IS_ERR(pdata->data_base)) {
+               dev_err(&pdev->dev, "%s: ERROR: failed to map data base\n",
+                       __func__);
+               return PTR_ERR(pdata->data_base);
+       }
+
+       pdata->board_flash_info = devm_kzalloc(&pdev->dev,
+                                              sizeof(*pdata->board_flash_info),
+                                              GFP_KERNEL);
+
+       /* Fill structs for each subnode (flash device) */
+       while ((pp = of_get_next_child(np, pp))) {
+               struct altera_epcq_flash_info *flash_info;
+
+               flash_info = &pdata->board_flash_info[i];
+               pdata->np[i] = pp;
+
+               /* Read bank id from DT */
+               if (of_get_property(pp, "reg", &id))
+                       pdata->board_flash_info[i].bank = id;
+               i++;
+       }
+       pdata->num_flashes = i;
+       return 0;
+}
+
+static int altera_epcq_setup_banks(struct platform_device *pdev,
+                                  u32 bank, struct device_node *np,
+                                  struct altera_epcq_plat_data *pdata) {
+       struct altera_epcq *dev = platform_get_drvdata(pdev);
+       struct mtd_part_parser_data ppdata = {};
+       struct altera_epcq_flash_info *flash_info;
+       struct altera_epcq_flash *flash;
+       struct mtd_partition *parts = NULL;
+       int count = 0;
+       int flash_index;
+       int ret = 0;
+       uint64_t size;
+       uint32_t sector_size;
+
+       flash_info = &pdata->board_flash_info[bank];
+       if (!flash_info)
+               return -ENODEV;
+
+       if (bank > pdata->num_flashes - 1)
+               return -EINVAL;
+
+       flash = devm_kzalloc(&pdev->dev, sizeof(*flash), GFP_KERNEL);
+       if (!flash)
+               return -ENOMEM;
+       flash->bank = bank;
+
+       mutex_init(&flash->lock);
+
+       /* verify whether flash is really present on board */
+       flash_index = altera_epcq_probe_flash(dev, bank);
+       if (flash_index < 0) {
+               dev_info(&dev->pdev->dev, "epcq:flash%d not found\n",
+                        flash->bank);
+               return flash_index;
+       }
+
+       dev->flash[bank] = flash;
+
+       size = flash_devices[flash_index].size_inbytes;
+       sector_size = flash_devices[flash_index].sectorsize_inbytes;
+       /*use do_div instead of plain div to avoid linker err*/
+       do_div(size, sector_size);
+       flash->num_sectors = size;
+
+       /*mtd framework */
+       flash->mtd.priv = dev;
+       flash->mtd.name = flash_devices[flash_index].name;
+       flash->mtd.type = MTD_NORFLASH;
+       flash->mtd.writesize = 1;
+       flash->mtd.flags = MTD_CAP_NORFLASH;
+       flash->mtd.size = flash_devices[flash_index].size_inbytes;
+       flash->mtd.erasesize = flash_devices[flash_index].sectorsize_inbytes;
+       flash->mtd._erase = altera_epcq_erase;
+       flash->mtd._read = altera_epcq_read;
+       flash->mtd._write = altera_epcq_write;
+       flash->mtd._lock = altera_epcq_lock;
+       flash->mtd._unlock = altera_epcq_unlock;
+
+       dev_info(&pdev->dev, "mtd .name=%s .size=0x%llx (%lluM)\n",
+                flash->mtd.name, (long long)flash->mtd.size,
+                (long long)(flash->mtd.size >> 20));
+
+       dev_info(&pdev->dev, ".erasesize = 0x%x(%uK)\n",
+                flash->mtd.erasesize, flash->mtd.erasesize >> 10);
+
+       ppdata.of_node = np;
+
+       ret = mtd_device_parse_register(&flash->mtd, NULL, &ppdata, parts,
+                                       count);
+       if (ret) {
+               dev_err(&pdev->dev, "Err MTD partition=%d\n", ret);
+               goto err;
+       }
+
+       return 0;
+
+err:
+       mtd_device_unregister(&flash->mtd);
+       return ret;
+}
+
+static int altera_epcq_probe(struct platform_device *pdev) {
+       struct device_node *np = pdev->dev.of_node;
+       struct altera_epcq_plat_data *pdata = NULL;
+       struct altera_epcq *dev;
+       int ret = 0;
+       int i;
+
+       if (!np) {
+               ret = -ENODEV;
+               dev_err(&pdev->dev, "no device found\n");
+               goto err;
+       }
+
+       pdata = devm_kzalloc(&pdev->dev,
+                            sizeof(struct altera_epcq_plat_data),
+                            GFP_KERNEL);
+
+       if (!pdata) {
+               ret = -ENOMEM;
+               dev_err(&pdev->dev, "%s: ERROR: no memory\n", __func__);
+               goto err;
+       }
+       ret = altera_epcq_probe_config_dt(pdev, np, pdata);
+       if (ret) {
+               ret = -ENODEV;
+               dev_err(&pdev->dev, "probe fail\n");
+               goto err;
+       }
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_ATOMIC);
+       if (!dev) {
+               ret = -ENOMEM;
+               dev_err(&pdev->dev, "mem alloc fail\n");
+               goto err;
+       }
+       mutex_init(&dev->lock);
+       dev->pdev = pdev;
+       dev->is_epcs = pdata->is_epcs;
+       dev->csr_base = pdata->csr_base;
+       dev->data_base = pdata->data_base;
+
+       /*check number of flashes*/
+       dev->num_flashes = pdata->num_flashes;
+       if (dev->num_flashes > MAX_NUM_FLASH_CHIP) {
+               dev_err(&pdev->dev, "exceeding max number of flashes\n");
+               dev->num_flashes = MAX_NUM_FLASH_CHIP;
+       }
+
+       /* check clock*/
+       dev->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(dev->clk)) {
+               ret = PTR_ERR(dev->clk);
+               goto err;
+       }
+       ret = clk_prepare_enable(dev->clk);
+       if (ret)
+               goto err;
+
+       platform_set_drvdata(pdev, dev);
+
+       /* loop for each serial flash which is connected to epcq */
+       for (i = 0; i < dev->num_flashes; i++) {
+               ret = altera_epcq_setup_banks(pdev, i, pdata->np[i], pdata);
+               if (ret) {
+                       dev_err(&pdev->dev, "bank setup failed\n");
+                       goto err_bank_setup;
+               }
+       }
+
+       return 0;
+
+err_bank_setup:
+       clk_disable_unprepare(dev->clk);
+err:
+       return ret;
+}
+
+static int altera_epcq_remove(struct platform_device *pdev) {
+       struct altera_epcq *dev;
+       struct altera_epcq_flash *flash;
+       int ret, i;
+
+       dev = platform_get_drvdata(pdev);
+
+       /* clean up for all nor flash */
+       for (i = 0; i < dev->num_flashes; i++) {
+               flash = dev->flash[i];
+               if (!flash)
+                       continue;
+
+               /* clean up mtd stuff */
+               ret = mtd_device_unregister(&flash->mtd);
+               if (ret)
+                       dev_err(&pdev->dev, "error removing mtd\n");
+       }
+
+       clk_disable_unprepare(dev->clk);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int altera_epcq_suspend(struct device *dev) {
+       struct altera_epcq *sdev = dev_get_drvdata(dev);
+
+       clk_disable_unprepare(sdev->clk);
+
+       return 0;
+}
+
+static int altera_epcq_resume(struct device *dev) {
+       struct altera_epcq *sdev = dev_get_drvdata(dev);
+       int ret = -EPERM;
+
+       ret = clk_prepare_enable(sdev->clk);
+
+       return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(altera_epcq_pm_ops, altera_epcq_suspend,
+                        altera_epcq_resume);
+#endif
+
+static const struct of_device_id altera_epcq_id_table[] = {
+       { .compatible = "altr,epcq-1.0" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, altera_epcq_id_table);
+
+static struct platform_driver altera_epcq_driver = {
+       .driver = {
+               .name = ALTERA_EPCQ_RESOURCE_NAME,
+               .bus = &platform_bus_type,
+               .owner = THIS_MODULE,
+               .of_match_table = altera_epcq_id_table, #ifdef CONFIG_PM
+               .pm = &altera_epcq_pm_ops,
+#endif
+       },
+       .probe = altera_epcq_probe,
+       .remove = altera_epcq_remove,
+};
+module_platform_driver(altera_epcq_driver);
+
+MODULE_AUTHOR("Viet Nga Dao <vndao@altera.com>");
+MODULE_DESCRIPTION("MTD Altera EPCQ Driver"); MODULE_LICENSE("GPL v2");
diff --git a/drivers/mtd/devices/altera_epcq.h b/drivers/mtd/devices/altera_epcq.h
new file mode 100644
index 0000000..c3d15e5
--- /dev/null
+++ b/drivers/mtd/devices/altera_epcq.h
@@ -0,0 +1,130 @@ 
+/*
+ * Copyright (C) 2014 Altera 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,