NAND access with parameter command

In the current implementation, to construct a NAND operation, such as
read/write/erase, the goldfish_nand driver requires several MMIO
operations that passes down the parameters like buffer address, length, etc.

This has visible impacts on emulator performance when running with hardware
virtualization enabled (e.g. HAXM) because each MMIO causes expensive
transition from the guest kernel to the kernel driver, and to the QEMU user
space in the end.

This change should not change the behavior of the goldfish_nand driver on
non-x86 kernels because this capability is not advertised on non-x86 qemu.
For x86, qemu already advertises this capability (NAND_DEV_FLAG_CMD_PARAMS_CAP).

Change-Id: I95fad86414ed73687268c9a613cfcb259a5ab10e
Signed-off-by: Zhang, Xiantao <xiantao.zhang@intel.com>
Signed-off-by: Xin, Xiaohui <xiaohui.xin@intel.com>
Signed-off-by: Jiang, Yunhong <yunhong.jiang@intel.com>
Signed-off-by: Nakajima, Jun <jun.nakajima@intel.com>
diff --git a/drivers/mtd/devices/goldfish_nand.c b/drivers/mtd/devices/goldfish_nand.c
index d83813e..7e2c227 100644
--- a/drivers/mtd/devices/goldfish_nand.c
+++ b/drivers/mtd/devices/goldfish_nand.c
@@ -29,10 +29,46 @@
 struct goldfish_nand {
 	spinlock_t              lock;
 	unsigned char __iomem  *base;
+	struct cmd_params       *cmd_params;
 	size_t                  mtd_count;
 	struct mtd_info         mtd[0];
 };
 
+static uint32_t goldfish_nand_cmd_with_params(struct mtd_info *mtd,
+			enum nand_cmd cmd, uint64_t addr, uint32_t len,
+			void *ptr, uint32_t *rv)
+{
+	uint32_t cmdp;
+	struct goldfish_nand *nand = mtd->priv;
+	struct cmd_params *cps = nand->cmd_params;
+	unsigned char __iomem  *base = nand->base;
+
+	if (cps == NULL)
+	    return -1;
+
+	switch(cmd) {
+	    case NAND_CMD_ERASE:
+		cmdp = NAND_CMD_ERASE_WITH_PARAMS;
+		break;
+	    case NAND_CMD_READ:
+		cmdp = NAND_CMD_READ_WITH_PARAMS;
+		break;
+	    case NAND_CMD_WRITE:
+		cmdp = NAND_CMD_WRITE_WITH_PARAMS;
+		break;
+	    default:
+		return -1;
+	}
+	cps->dev = mtd - nand->mtd;
+	cps->addr_high = (uint32_t)(addr >> 32);
+	cps->addr_low = (uint32_t)addr;
+	cps->transfer_size = len;
+	cps->data = (uint32_t)ptr;
+	writel(cmdp, base + NAND_COMMAND);
+	*rv = cps->result;
+	return 0;
+}
+
 static uint32_t goldfish_nand_cmd(struct mtd_info *mtd, enum nand_cmd cmd,
                               uint64_t addr, uint32_t len, void *ptr)
 {
@@ -42,13 +78,16 @@
 	unsigned char __iomem  *base = nand->base;
 
 	spin_lock_irqsave(&nand->lock, irq_flags);
-	writel(mtd - nand->mtd, base + NAND_DEV);
-	writel((uint32_t)(addr >> 32), base + NAND_ADDR_HIGH);
-	writel((uint32_t)addr, base + NAND_ADDR_LOW);
-	writel(len, base + NAND_TRANSFER_SIZE);
-	writel((unsigned long)ptr, base + NAND_DATA);
-	writel(cmd, base + NAND_COMMAND);
-	rv = readl(base + NAND_RESULT);
+	if (goldfish_nand_cmd_with_params(mtd, cmd, addr, len, ptr, &rv))
+	{
+	    writel(mtd - nand->mtd, base + NAND_DEV);
+	    writel((uint32_t)(addr >> 32), base + NAND_ADDR_HIGH);
+	    writel((uint32_t)addr, base + NAND_ADDR_LOW);
+	    writel(len, base + NAND_TRANSFER_SIZE);
+	    writel((unsigned long)ptr, base + NAND_DATA);
+	    writel(cmd, base + NAND_COMMAND);
+	    rv = readl(base + NAND_RESULT);
+	}
 	spin_unlock_irqrestore(&nand->lock, irq_flags);
 	return rv;
 }
@@ -245,6 +284,21 @@
 	return -EINVAL;
 }
 
+static int nand_setup_cmd_params(struct goldfish_nand *nand)
+{
+	uint64_t paddr;
+	unsigned char __iomem  *base = nand->base;
+
+	nand->cmd_params = kmalloc(sizeof(struct cmd_params), GFP_KERNEL);
+	if (!nand->cmd_params)
+	    return -1;
+
+	paddr = __pa(nand->cmd_params);
+	writel((uint32_t)(paddr >> 32), base + NAND_CMD_PARAMS_ADDR_HIGH);
+	writel((uint32_t)paddr, base + NAND_CMD_PARAMS_ADDR_LOW);
+	return 0;
+}
+
 static int goldfish_nand_init_device(struct goldfish_nand *nand, int id)
 {
 	uint32_t name_len;
@@ -293,6 +347,8 @@
 	mtd->flags = MTD_CAP_NANDFLASH;
 	if(flags & NAND_DEV_FLAG_READ_ONLY)
 		mtd->flags &= ~MTD_WRITEABLE;
+	if(flags & NAND_DEV_FLAG_CMD_PARAMS_CAP)
+	    nand_setup_cmd_params(nand);
 
 	mtd->owner = THIS_MODULE;
 	mtd->erase = goldfish_nand_erase;
@@ -389,6 +445,8 @@
 			kfree(nand->mtd[i].name);
 		}
 	}
+	if (nand->cmd_params)
+	    kfree(nand->cmd_params);
 	iounmap(nand->base);
 	kfree(nand);
 	return 0;
diff --git a/drivers/mtd/devices/goldfish_nand_reg.h b/drivers/mtd/devices/goldfish_nand_reg.h
index 7c17a44..0fde9ec 100644
--- a/drivers/mtd/devices/goldfish_nand_reg.h
+++ b/drivers/mtd/devices/goldfish_nand_reg.h
@@ -22,11 +22,15 @@
 	NAND_CMD_WRITE,
 	NAND_CMD_ERASE,
 	NAND_CMD_BLOCK_BAD_GET, // NAND_RESULT is 1 if block is bad, 0 if it is not
-	NAND_CMD_BLOCK_BAD_SET
+	NAND_CMD_BLOCK_BAD_SET,
+	NAND_CMD_READ_WITH_PARAMS,
+	NAND_CMD_WRITE_WITH_PARAMS,
+	NAND_CMD_ERASE_WITH_PARAMS
 };
 
 enum nand_dev_flags {
-	NAND_DEV_FLAG_READ_ONLY = 0x00000001
+    NAND_DEV_FLAG_READ_ONLY = 0x00000001,
+    NAND_DEV_FLAG_CMD_PARAMS_CAP = 0x00000002,
 };
 
 #define NAND_VERSION_CURRENT (1)
@@ -53,6 +57,16 @@
 	NAND_TRANSFER_SIZE  = 0x04c,
 	NAND_ADDR_LOW       = 0x050,
 	NAND_ADDR_HIGH      = 0x054,
+	NAND_CMD_PARAMS_ADDR_LOW = 0x058,
+	NAND_CMD_PARAMS_ADDR_HIGH = 0x05c,
 };
 
+struct cmd_params{
+	uint32_t dev;
+	uint32_t addr_low;
+	uint32_t addr_high;
+	uint32_t transfer_size;
+	uint32_t data;
+	uint32_t result;
+};
 #endif