[RFC PATCH v2 2/6] mtd: spi-nor: atmel-quadspi: Add spi-mem support to atmel-quadspi

2018-06-27 Thread Piotr Bugalski
This patch adds new interface to existing driver. New code is not used yet,
it will be enabled later.
Changes are prepared in small steps to keep patches readable.

Suggested-by: Boris Brezillon 
Signed-off-by: Piotr Bugalski 
---
 drivers/mtd/spi-nor/atmel-quadspi.c | 205 
 1 file changed, 205 insertions(+)

diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c 
b/drivers/mtd/spi-nor/atmel-quadspi.c
index bdbfaa632dbf..c36fcecd569a 100644
--- a/drivers/mtd/spi-nor/atmel-quadspi.c
+++ b/drivers/mtd/spi-nor/atmel-quadspi.c
@@ -2,8 +2,10 @@
  * Driver for Atmel QSPI Controller
  *
  * Copyright (C) 2015 Atmel Corporation
+ * Copyright (C) 2018 Cryptera A/S
  *
  * Author: Cyrille Pitchen 
+ * Author: Piotr Bugalski 
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -35,6 +37,7 @@
 
 #include 
 #include 
+#include 
 
 /* QSPI register offsets */
 #define QSPI_CR  0x  /* Control Register */
@@ -186,6 +189,23 @@ struct atmel_qspi_command {
void*rx_buf;
 };
 
+struct qspi_mode {
+   u8 cmd_buswidth;
+   u8 addr_buswidth;
+   u8 data_buswidth;
+   u32 config;
+};
+
+static const struct qspi_mode sama5d2_qspi_modes[] = {
+   { 1, 1, 1, QSPI_IFR_WIDTH_SINGLE_BIT_SPI },
+   { 1, 1, 2, QSPI_IFR_WIDTH_DUAL_OUTPUT },
+   { 1, 1, 4, QSPI_IFR_WIDTH_QUAD_OUTPUT },
+   { 1, 2, 2, QSPI_IFR_WIDTH_DUAL_IO },
+   { 1, 4, 4, QSPI_IFR_WIDTH_QUAD_IO },
+   { 2, 2, 2, QSPI_IFR_WIDTH_DUAL_CMD },
+   { 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD },
+};
+
 /* Register access functions */
 static inline u32 qspi_readl(struct atmel_qspi *aq, u32 reg)
 {
@@ -197,6 +217,190 @@ static inline void qspi_writel(struct atmel_qspi *aq, u32 
reg, u32 value)
writel_relaxed(value, aq->regs + reg);
 }
 
+static inline bool is_compatible(const struct spi_mem_op *op,
+const struct qspi_mode *mode)
+{
+   if (op->cmd.buswidth != mode->cmd_buswidth)
+   return false;
+
+   if (op->addr.nbytes && op->addr.buswidth != mode->addr_buswidth)
+   return false;
+
+   if (op->data.nbytes && op->data.buswidth != mode->data_buswidth)
+   return false;
+
+   return true;
+}
+
+static int find_mode(const struct spi_mem_op *op)
+{
+   u32 i;
+
+   for (i = 0; i < ARRAY_SIZE(sama5d2_qspi_modes); i++)
+   if (is_compatible(op, _qspi_modes[i]))
+   return i;
+
+   return -1;
+}
+
+static bool atmel_qspi_supports_op(struct spi_mem *mem,
+  const struct spi_mem_op *op)
+{
+   if (find_mode(op) < 0)
+   return false;
+
+   /* special case not supported by hardware */
+   if (op->addr.nbytes == 2 && op->cmd.buswidth != op->addr.buswidth &&
+   op->dummy.nbytes == 0)
+   return false;
+
+   return true;
+}
+
+static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+   struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->master);
+   int mode;
+   u32 dummy_cycles = 0;
+   u32 iar, icr, ifr, sr;
+   int err = 0;
+
+   iar = 0;
+   icr = QSPI_ICR_INST(op->cmd.opcode);
+   ifr = QSPI_IFR_INSTEN;
+
+   qspi_writel(aq, QSPI_MR, QSPI_MR_SMM);
+
+   mode = find_mode(op);
+   if (mode < 0)
+   return -ENOTSUPP;
+
+   ifr |= sama5d2_qspi_modes[mode].config;
+
+   if (op->dummy.buswidth && op->dummy.nbytes)
+   dummy_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;
+
+   if (op->addr.buswidth) {
+   switch (op->addr.nbytes) {
+   case 0:
+   break;
+   case 1:
+   ifr |= QSPI_IFR_OPTEN | QSPI_IFR_OPTL_8BIT;
+   icr |= QSPI_ICR_OPT(op->addr.val & 0xff);
+   break;
+   case 2:
+   if (dummy_cycles < 8 / op->addr.buswidth) {
+   ifr &= ~QSPI_IFR_INSTEN;
+   ifr |= QSPI_IFR_ADDREN;
+   iar = (op->cmd.opcode << 16) |
+   (op->addr.val & 0x);
+   } else {
+   ifr |= QSPI_IFR_ADDREN;
+   iar = (op->addr.val << 8) & 0xff;
+   dummy_cycles -= 8 / op->addr.buswidth;
+   }
+   break;
+   case 3:
+   ifr |= QSPI_IFR_ADDREN;
+   iar = op->addr.val & 0xff;
+   break;
+   case 4:
+   ifr |= QSPI_IFR_ADDREN | QSPI_IFR_ADDRL;
+   iar = op->addr.val & 0x7ff;
+   break;
+   default:
+   

[RFC PATCH v2 2/6] mtd: spi-nor: atmel-quadspi: Add spi-mem support to atmel-quadspi

2018-06-27 Thread Piotr Bugalski
This patch adds new interface to existing driver. New code is not used yet,
it will be enabled later.
Changes are prepared in small steps to keep patches readable.

Suggested-by: Boris Brezillon 
Signed-off-by: Piotr Bugalski 
---
 drivers/mtd/spi-nor/atmel-quadspi.c | 205 
 1 file changed, 205 insertions(+)

diff --git a/drivers/mtd/spi-nor/atmel-quadspi.c 
b/drivers/mtd/spi-nor/atmel-quadspi.c
index bdbfaa632dbf..c36fcecd569a 100644
--- a/drivers/mtd/spi-nor/atmel-quadspi.c
+++ b/drivers/mtd/spi-nor/atmel-quadspi.c
@@ -2,8 +2,10 @@
  * Driver for Atmel QSPI Controller
  *
  * Copyright (C) 2015 Atmel Corporation
+ * Copyright (C) 2018 Cryptera A/S
  *
  * Author: Cyrille Pitchen 
+ * Author: Piotr Bugalski 
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -35,6 +37,7 @@
 
 #include 
 #include 
+#include 
 
 /* QSPI register offsets */
 #define QSPI_CR  0x  /* Control Register */
@@ -186,6 +189,23 @@ struct atmel_qspi_command {
void*rx_buf;
 };
 
+struct qspi_mode {
+   u8 cmd_buswidth;
+   u8 addr_buswidth;
+   u8 data_buswidth;
+   u32 config;
+};
+
+static const struct qspi_mode sama5d2_qspi_modes[] = {
+   { 1, 1, 1, QSPI_IFR_WIDTH_SINGLE_BIT_SPI },
+   { 1, 1, 2, QSPI_IFR_WIDTH_DUAL_OUTPUT },
+   { 1, 1, 4, QSPI_IFR_WIDTH_QUAD_OUTPUT },
+   { 1, 2, 2, QSPI_IFR_WIDTH_DUAL_IO },
+   { 1, 4, 4, QSPI_IFR_WIDTH_QUAD_IO },
+   { 2, 2, 2, QSPI_IFR_WIDTH_DUAL_CMD },
+   { 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD },
+};
+
 /* Register access functions */
 static inline u32 qspi_readl(struct atmel_qspi *aq, u32 reg)
 {
@@ -197,6 +217,190 @@ static inline void qspi_writel(struct atmel_qspi *aq, u32 
reg, u32 value)
writel_relaxed(value, aq->regs + reg);
 }
 
+static inline bool is_compatible(const struct spi_mem_op *op,
+const struct qspi_mode *mode)
+{
+   if (op->cmd.buswidth != mode->cmd_buswidth)
+   return false;
+
+   if (op->addr.nbytes && op->addr.buswidth != mode->addr_buswidth)
+   return false;
+
+   if (op->data.nbytes && op->data.buswidth != mode->data_buswidth)
+   return false;
+
+   return true;
+}
+
+static int find_mode(const struct spi_mem_op *op)
+{
+   u32 i;
+
+   for (i = 0; i < ARRAY_SIZE(sama5d2_qspi_modes); i++)
+   if (is_compatible(op, _qspi_modes[i]))
+   return i;
+
+   return -1;
+}
+
+static bool atmel_qspi_supports_op(struct spi_mem *mem,
+  const struct spi_mem_op *op)
+{
+   if (find_mode(op) < 0)
+   return false;
+
+   /* special case not supported by hardware */
+   if (op->addr.nbytes == 2 && op->cmd.buswidth != op->addr.buswidth &&
+   op->dummy.nbytes == 0)
+   return false;
+
+   return true;
+}
+
+static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+   struct atmel_qspi *aq = spi_controller_get_devdata(mem->spi->master);
+   int mode;
+   u32 dummy_cycles = 0;
+   u32 iar, icr, ifr, sr;
+   int err = 0;
+
+   iar = 0;
+   icr = QSPI_ICR_INST(op->cmd.opcode);
+   ifr = QSPI_IFR_INSTEN;
+
+   qspi_writel(aq, QSPI_MR, QSPI_MR_SMM);
+
+   mode = find_mode(op);
+   if (mode < 0)
+   return -ENOTSUPP;
+
+   ifr |= sama5d2_qspi_modes[mode].config;
+
+   if (op->dummy.buswidth && op->dummy.nbytes)
+   dummy_cycles = op->dummy.nbytes * 8 / op->dummy.buswidth;
+
+   if (op->addr.buswidth) {
+   switch (op->addr.nbytes) {
+   case 0:
+   break;
+   case 1:
+   ifr |= QSPI_IFR_OPTEN | QSPI_IFR_OPTL_8BIT;
+   icr |= QSPI_ICR_OPT(op->addr.val & 0xff);
+   break;
+   case 2:
+   if (dummy_cycles < 8 / op->addr.buswidth) {
+   ifr &= ~QSPI_IFR_INSTEN;
+   ifr |= QSPI_IFR_ADDREN;
+   iar = (op->cmd.opcode << 16) |
+   (op->addr.val & 0x);
+   } else {
+   ifr |= QSPI_IFR_ADDREN;
+   iar = (op->addr.val << 8) & 0xff;
+   dummy_cycles -= 8 / op->addr.buswidth;
+   }
+   break;
+   case 3:
+   ifr |= QSPI_IFR_ADDREN;
+   iar = op->addr.val & 0xff;
+   break;
+   case 4:
+   ifr |= QSPI_IFR_ADDREN | QSPI_IFR_ADDRL;
+   iar = op->addr.val & 0x7ff;
+   break;
+   default:
+