This patch does the below
--> Adds support for readback of pl configuration data
--> Adds support for readback of pl configuration registers

Usage:
Readback of PL configuration registers
        cat /sys/kernel/debug/fpga/fpga0/image
Readback of PL configuration data
        echo 1 > /sys/module/zynqmp_fpga/parameters/readback_type
        cat /sys/kernel/debug/fpga/fpga0/image

Signed-off-by: Appana Durga Kedareswara rao <[email protected]>
---
Changes for v3:
--> Added support for pl configuration data readback
--> Improved the pl configuration register readback logic.
Changes for v2:
--> Removed locks from the read_ops as lock handling is done in the framework.

 drivers/fpga/zynq-fpga.c | 400 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 400 insertions(+)

diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c
index 70b15b3..5f1a1aa 100644
--- a/drivers/fpga/zynq-fpga.c
+++ b/drivers/fpga/zynq-fpga.c
@@ -31,6 +31,7 @@
 #include <linux/regmap.h>
 #include <linux/string.h>
 #include <linux/scatterlist.h>
+#include <linux/seq_file.h>
 
 /* Offsets into SLCR regmap */
 
@@ -127,6 +128,72 @@
 /* Disable global resets */
 #define FPGA_RST_NONE_MASK             0x0
 
+static bool readback_type;
+module_param(readback_type, bool, 0644);
+MODULE_PARM_DESC(readback_type,
+               "readback_type 0-configuration register read "
+               "1- configuration data read (default: 0)");
+
+/**
+ * struct zynq_configreg - Configuration register offsets
+ * @reg:       Name of the configuration register.
+ * @offset:    Register offset.
+ */
+struct zynq_configreg {
+       char *reg;
+       u32 offset;
+};
+
+static struct zynq_configreg cfgreg[] = {
+       {.reg = "CRC",          .offset = 0},
+       {.reg = "FAR",          .offset = 1},
+       {.reg = "FDRI",         .offset = 2},
+       {.reg = "FDRO",         .offset = 3},
+       {.reg = "CMD",          .offset = 4},
+       {.reg = "CTRL0",        .offset = 5},
+       {.reg = "MASK",         .offset = 6},
+       {.reg = "STAT",         .offset = 7},
+       {.reg = "LOUT",         .offset = 8},
+       {.reg = "COR0",         .offset = 9},
+       {.reg = "MFWR",         .offset = 10},
+       {.reg = "CBC",          .offset = 11},
+       {.reg = "IDCODE",       .offset = 12},
+       {.reg = "AXSS",         .offset = 13},
+       {.reg = "COR1",         .offset = 14},
+       {.reg = "WBSTR",        .offset = 16},
+       {.reg = "TIMER",        .offset = 17},
+       {.reg = "BOOTSTS",      .offset = 22},
+       {.reg = "CTRL1",        .offset = 24},
+       {}
+};
+
+/* Masks for Configuration registers */
+#define FAR_ADDR_MASK          0x00000000
+#define RCFG_CMD_MASK          0x00000004
+#define START_CMD_MASK         0x00000005
+#define RCRC_CMD_MASK          0x00000007
+#define SHUTDOWN_CMD_MASK      0x0000000B
+#define DESYNC_WORD_MASK       0x0000000D
+#define BUSWIDTH_SYNCWORD_MASK 0x000000BB
+#define NOOP_WORD_MASK         0x20000000
+#define BUSWIDTH_DETECT_MASK   0x11220044
+#define SYNC_WORD_MASK         0xAA995566
+#define DUMMY_WORD_MASK                0xFFFFFFFF
+
+#define TYPE_HDR_SHIFT         29
+#define TYPE_REG_SHIFT         13
+#define TYPE_OP_SHIFT          27
+#define TYPE_OPCODE_NOOP       0
+#define TYPE_OPCODE_READ       1
+#define TYPE_OPCODE_WRITE      2
+#define TYPE_FAR_OFFSET                1
+#define TYPE_FDRO_OFFSET       3
+#define TYPE_CMD_OFFSET                4
+
+#define READ_DMA_SIZE          0x200
+#define DUMMY_FRAMES_SIZE      0x28
+#define SLCR_PCAP_FREQ         10000000
+
 struct zynq_fpga_priv {
        int irq;
        struct clk *clk;
@@ -140,6 +207,7 @@ struct zynq_fpga_priv {
        struct scatterlist *cur_sg;
 
        struct completion dma_done;
+       u32 size;
 };
 
 static inline void zynq_fpga_write(struct zynq_fpga_priv *priv, u32 offset,
@@ -164,6 +232,27 @@ static inline void zynq_fpga_set_irq(struct zynq_fpga_priv 
*priv, u32 enable)
        zynq_fpga_write(priv, INT_MASK_OFFSET, ~enable);
 }
 
+static void zynq_fpga_dma_xfer(struct zynq_fpga_priv *priv, u32 srcaddr,
+                              u32 srclen, u32 dstaddr, u32 dstlen)
+{
+       zynq_fpga_write(priv, DMA_SRC_ADDR_OFFSET, srcaddr);
+       zynq_fpga_write(priv, DMA_DST_ADDR_OFFSET, dstaddr);
+       zynq_fpga_write(priv, DMA_SRC_LEN_OFFSET, srclen);
+       zynq_fpga_write(priv, DMA_DEST_LEN_OFFSET, dstlen);
+}
+
+static int zynq_fpga_wait_fordone(struct zynq_fpga_priv *priv)
+{
+       u32 status;
+       int ret;
+
+       ret = zynq_fpga_poll_timeout(priv, INT_STS_OFFSET, status,
+                                    status & IXR_D_P_DONE_MASK,
+                                    INIT_POLL_DELAY,
+                                    INIT_POLL_TIMEOUT);
+       return ret;
+}
+
 /* Must be called with dma_lock held */
 static void zynq_step_dma(struct zynq_fpga_priv *priv)
 {
@@ -192,6 +281,7 @@ static void zynq_step_dma(struct zynq_fpga_priv *priv)
                        priv->dma_elm++;
                }
 
+               priv->size += len;
                zynq_fpga_write(priv, DMA_SRC_ADDR_OFFSET, addr);
                zynq_fpga_write(priv, DMA_DST_ADDR_OFFSET, DMA_INVALID_ADDRESS);
                zynq_fpga_write(priv, DMA_SRC_LEN_OFFSET, len / 4);
@@ -401,6 +491,7 @@ static int zynq_fpga_ops_write(struct fpga_manager *mgr, 
struct sg_table *sgt)
        int i;
 
        priv = mgr->priv;
+       priv->size = 0;
 
        /* The hardware can only DMA multiples of 4 bytes, and it requires the
         * starting addresses to be aligned to 64 bits (UG585 pg 212).
@@ -546,12 +637,321 @@ static enum fpga_mgr_states zynq_fpga_ops_state(struct 
fpga_manager *mgr)
        return FPGA_MGR_STATE_UNKNOWN;
 }
 
+static int zynq_type1_pkt(u8 reg, u8 opcode, u16 size)
+{
+       /*
+        * Type 1 Packet Header Format
+        * The header section is always a 32-bit word.
+        *
+        * HeaderType | Opcode | Register Address | Reserved | Word Count
+        * [31:29]      [28:27]         [26:13]      [12:11]     [10:0]
+        * --------------------------------------------------------------
+        *   001          xx      RRRRRRRRRxxxxx        RR      xxxxxxxxxxx
+        *
+        * @R: means the bit is not used and reserved for future use.
+        * The reserved bits should be written as 0s.
+        *
+        * Generating the Type 1 packet header which involves sifting of Type1
+        * Header Mask, Register value and the OpCode which is 01 in this case
+        * as only read operation is to be carried out and then performing OR
+        * operation with the Word Length.
+        */
+       return (((1 << TYPE_HDR_SHIFT) |
+               (reg << TYPE_REG_SHIFT) |
+               (opcode << TYPE_OP_SHIFT)) | size);
+
+}
+
+/****************************************************************************/
+/**
+ *
+ * Generates a Type 2 packet header that reads back the requested Configuration
+ * register.
+ *
+ * @param        OpCode is the read/write operation code.
+ * @param        Size is the size of the word to be read.
+ *
+ * @return       Type 2 packet header to read the specified register
+ *
+ * @note         None.
+ *
+ *****************************************************************************/
+static int zynq_type2_pkt(u8 OpCode, u32 Size)
+{
+       /*
+        * Type 2 Packet Header Format
+        * The header section is always a 32-bit word.
+        *
+        * HeaderType | Opcode |  Word Count
+        * [31:29]      [28:27]         [26:0]
+        * --------------------------------------------------------------
+        *   010          xx      xxxxxxxxxxxxx
+        *
+        * @R: means the bit is not used and reserved for future use.
+        * The reserved bits should be written as 0s.
+        *
+        * Generating the Type 2 packet header which involves sifting of Type 2
+        * Header Mask, OpCode and then performing OR
+        * operation with the Word Length.
+        */
+       return (((2 << TYPE_HDR_SHIFT) |
+               (OpCode << TYPE_OP_SHIFT)) | Size);
+}
+
+static int zynq_fpga_read_cfgdata(struct fpga_manager *mgr,
+                                 struct seq_file *s)
+{
+       struct zynq_fpga_priv *priv;
+       int ret = 0, i, cmdindex, clk_rate;
+       unsigned int *buf;
+       dma_addr_t dma_addr;
+       u32 intr_status, status;
+       size_t size;
+
+       priv = mgr->priv;
+       size = priv->size + READ_DMA_SIZE + DUMMY_FRAMES_SIZE;
+       buf = dma_zalloc_coherent(mgr->dev.parent, size,
+                                &dma_addr, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       seq_puts(s, "zynq FPGA Configuration data contents are\n");
+
+       /*
+        * There is no h/w flow control for pcap read
+        * to prevent the FIFO from over flowing, reduce
+        * the PCAP operating frequency.
+        */
+       clk_rate = clk_get_rate(priv->clk);
+       ret = clk_set_rate(priv->clk, SLCR_PCAP_FREQ);
+       if (ret) {
+               dev_err(&mgr->dev, "Unable to reduce the PCAP freq\n");
+               goto free_dmabuf;
+       }
+
+       ret = zynq_fpga_poll_timeout(priv, STATUS_OFFSET, status,
+                                    status & STATUS_PCFG_INIT_MASK,
+                                    INIT_POLL_DELAY,
+                                    INIT_POLL_TIMEOUT);
+       if (ret) {
+               dev_err(&mgr->dev, "Timeout waiting for PCFG_INIT\n");
+               goto restore_pcap_clk;
+       }
+
+       cmdindex = 0;
+       buf[cmdindex++] = DUMMY_WORD_MASK;
+       buf[cmdindex++] = BUSWIDTH_SYNCWORD_MASK;
+       buf[cmdindex++] = BUSWIDTH_DETECT_MASK;
+       buf[cmdindex++] = DUMMY_WORD_MASK;
+       buf[cmdindex++] = SYNC_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET, TYPE_OPCODE_WRITE,
+                                        1);
+       buf[cmdindex++] = SHUTDOWN_CMD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET, TYPE_OPCODE_WRITE,
+                                        1);
+       buf[cmdindex++] = RCRC_CMD_MASK;
+       for (i = 0; i < 6; i++)
+               buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET, TYPE_OPCODE_WRITE,
+                                        1);
+       buf[cmdindex++] = RCFG_CMD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_FAR_OFFSET, TYPE_OPCODE_WRITE,
+                                        1);
+       buf[cmdindex++] = FAR_ADDR_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_FDRO_OFFSET, TYPE_OPCODE_READ,
+                                        0);
+       buf[cmdindex++] = zynq_type2_pkt(TYPE_OPCODE_READ, priv->size/4);
+       for (i = 0; i < 32; i++)
+               buf[cmdindex++] = NOOP_WORD_MASK;
+
+       intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
+       zynq_fpga_write(priv, INT_STS_OFFSET, intr_status);
+
+       /* Write to PCAP */
+       zynq_fpga_dma_xfer(priv, dma_addr, cmdindex,
+                          DMA_INVALID_ADDRESS, 0);
+       ret = zynq_fpga_wait_fordone(priv);
+       if (ret) {
+               dev_err(&mgr->dev, "SRCDMA: Timeout waiting for D_P_DONE\n");
+               goto restore_pcap_clk;
+       }
+       intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
+       zynq_fpga_write(priv, INT_STS_OFFSET, intr_status);
+
+       /* READ From PACP */
+       zynq_fpga_dma_xfer(priv, DMA_INVALID_ADDRESS, 0,
+                          dma_addr + READ_DMA_SIZE, priv->size/4);
+       ret = zynq_fpga_wait_fordone(priv);
+       if (ret) {
+               dev_err(&mgr->dev, "DSTDMA: Timeout waiting for D_P_DONE\n");
+               goto restore_pcap_clk;
+       }
+       intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
+       zynq_fpga_write(priv, INT_STS_OFFSET, intr_status);
+
+       /* Write to PCAP */
+       cmdindex = 0;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET,
+                                        TYPE_OPCODE_WRITE, 1);
+       buf[cmdindex++] = START_CMD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET,
+                                        TYPE_OPCODE_WRITE, 1);
+       buf[cmdindex++] = RCRC_CMD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET,
+                                        TYPE_OPCODE_WRITE, 1);
+       buf[cmdindex++] = DESYNC_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+
+       zynq_fpga_dma_xfer(priv, dma_addr, cmdindex,
+                          DMA_INVALID_ADDRESS, 0);
+       ret = zynq_fpga_wait_fordone(priv);
+       if (ret) {
+               dev_err(&mgr->dev, "SRCDMA1: Timeout waiting for D_P_DONE\n");
+               goto restore_pcap_clk;
+       }
+
+       seq_write(s, &buf[READ_DMA_SIZE/4], priv->size);
+
+restore_pcap_clk:
+       clk_set_rate(priv->clk, clk_rate);
+free_dmabuf:
+       dma_free_coherent(mgr->dev.parent, size, buf,
+                         dma_addr);
+       return ret;
+}
+
+static int zynq_fpga_getconfigreg(struct fpga_manager *mgr, u8 reg,
+                                 dma_addr_t dma_addr, int *buf)
+{
+       struct zynq_fpga_priv *priv;
+       int ret = 0, cmdindex, src_dmaoffset;
+       u32 intr_status, status;
+
+       priv = mgr->priv;
+
+       src_dmaoffset = 0x8;
+       cmdindex = 2;
+       buf[cmdindex++] = DUMMY_WORD_MASK;
+       buf[cmdindex++] = BUSWIDTH_SYNCWORD_MASK;
+       buf[cmdindex++] = BUSWIDTH_DETECT_MASK;
+       buf[cmdindex++] = DUMMY_WORD_MASK;
+       buf[cmdindex++] = SYNC_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = zynq_type1_pkt(reg, TYPE_OPCODE_READ, 1);
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+
+       ret = zynq_fpga_poll_timeout(priv, STATUS_OFFSET, status,
+                                    status & STATUS_PCFG_INIT_MASK,
+                                    INIT_POLL_DELAY,
+                                    INIT_POLL_TIMEOUT);
+       if (ret) {
+               dev_err(&mgr->dev, "Timeout waiting for PCFG_INIT\n");
+               goto out;
+       }
+
+       /* Write to PCAP */
+       intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
+       zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK);
+
+       zynq_fpga_dma_xfer(priv, dma_addr + src_dmaoffset, cmdindex,
+                          DMA_INVALID_ADDRESS, 0);
+       ret = zynq_fpga_wait_fordone(priv);
+       if (ret) {
+               dev_err(&mgr->dev, "SRCDMA: Timeout waiting for D_P_DONE\n");
+               goto out;
+       }
+       zynq_fpga_set_irq(priv, intr_status);
+
+       /* READ From PACP */
+       zynq_fpga_dma_xfer(priv, DMA_INVALID_ADDRESS, 0, dma_addr, 1);
+       ret = zynq_fpga_wait_fordone(priv);
+       if (ret) {
+               dev_err(&mgr->dev, "DSTDMA: Timeout waiting for D_P_DONE\n");
+               goto out;
+       }
+
+       /* Write to PCAP */
+       cmdindex = 2;
+       buf[cmdindex++] = zynq_type1_pkt(TYPE_CMD_OFFSET,
+                                        TYPE_OPCODE_WRITE, 1);
+       buf[cmdindex++] = DESYNC_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       buf[cmdindex++] = NOOP_WORD_MASK;
+       zynq_fpga_dma_xfer(priv, dma_addr + src_dmaoffset, cmdindex,
+                          DMA_INVALID_ADDRESS, 0);
+       ret = zynq_fpga_wait_fordone(priv);
+       if (ret)
+               dev_err(&mgr->dev, "SRCDMA1: Timeout waiting for D_P_DONE\n");
+out:
+       return ret;
+}
+
+static int zynq_fpga_read_cfgreg(struct fpga_manager *mgr,
+                                struct seq_file *s)
+{
+       int ret = 0;
+       unsigned int *buf;
+       dma_addr_t dma_addr;
+       struct zynq_configreg *p = cfgreg;
+
+       buf = dma_zalloc_coherent(mgr->dev.parent, READ_DMA_SIZE,
+                                &dma_addr, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       seq_puts(s, "zynq FPGA Configuration register contents are\n");
+
+       while (p->reg) {
+               ret = zynq_fpga_getconfigreg(mgr, p->offset, dma_addr, buf);
+               if (ret)
+                       goto free_dmabuf;
+               seq_printf(s, "%s --> \t %x \t\r\n", p->reg, buf[0]);
+               p++;
+       }
+
+free_dmabuf:
+       dma_free_coherent(mgr->dev.parent, READ_DMA_SIZE, buf,
+                         dma_addr);
+       return ret;
+}
+
+static int zynq_fpga_ops_read(struct fpga_manager *mgr, struct seq_file *s)
+{
+       struct zynq_fpga_priv *priv;
+       int ret;
+
+       priv = mgr->priv;
+
+       ret = clk_enable(priv->clk);
+       if (ret)
+               return ret;
+
+       if (readback_type)
+               ret = zynq_fpga_read_cfgdata(mgr, s);
+       else
+               ret = zynq_fpga_read_cfgreg(mgr, s);
+
+       clk_disable(priv->clk);
+
+       return ret;
+}
+
 static const struct fpga_manager_ops zynq_fpga_ops = {
        .initial_header_size = 128,
        .state = zynq_fpga_ops_state,
        .write_init = zynq_fpga_ops_write_init,
        .write_sg = zynq_fpga_ops_write,
        .write_complete = zynq_fpga_ops_write_complete,
+       .read = zynq_fpga_ops_read,
 };
 
 static int zynq_fpga_probe(struct platform_device *pdev)
-- 
2.7.4

Reply via email to