From: Ulf Samuelsson <u...@emagii.com>

Signed-off-by: Ulf Samuelsson <u...@emagii.com>
---
 drivers/mtd/fpga/Kconfig      |  47 ++++++
 drivers/mtd/fpga/Makefile     |   6 +
 drivers/mtd/fpga/cyclone_10.c | 278 ++++++++++++++++++++++++++++++++++
 3 files changed, 331 insertions(+)
 create mode 100644 drivers/mtd/fpga/Kconfig
 create mode 100644 drivers/mtd/fpga/Makefile
 create mode 100644 drivers/mtd/fpga/cyclone_10.c

diff --git a/drivers/mtd/fpga/Kconfig b/drivers/mtd/fpga/Kconfig
new file mode 100644
index 0000000000..e3aa8c4522
--- /dev/null
+++ b/drivers/mtd/fpga/Kconfig
@@ -0,0 +1,47 @@
+menu "SPI FPGA Support"
+
+config DM_SPI_FPGA
+       bool "Enable Driver Model for FPGA configuration"
+       depends on DM && DM_SPI
+       imply SPI_FPGA
+       help
+         Enable driver model for FPGAs configurable using SPI.
+         This SPI FPGA interface
+         (spi_fpga_probe(), spi_fpga_write(), etc.) is then
+         implemented by the SPI FPGA uclass.
+         There is one standard SPI FPGA driver which knows how to probe
+         chips supported by U-Boot. The uclass interface is defined in
+         include/spi_fpga.h
+         SPI and SPI FPGA must be enabled together
+         (it is not possible to use driver model for one and not the other).
+
+if DM_SPI_FPGA
+
+config SPI_FPGA_MTD
+       bool "SPI FPGA MTD support"
+       depends on MTD
+       help
+         Enable the MTD support for the FPGA SPI Passive Serial,
+         This allows mtd_write commands to load an FPGA using passive serial
+         If unsure, say N
+
+config SPI_FPGA_INTEL
+       bool "Intel/Altera FPGA Passive Serial configuration using SPI"
+       help
+         Add support for various Intel SPI FPGA chips
+
+config SPI_FPGA_XILINX
+       bool "Xilinx FPGA Passive Serial configuration using SPI"
+       help
+         Add support for various Xilinx FPGA chips
+
+config SPI_FPGA_CYCLONE10
+       bool "Cyclone 10 SPI FPGA MTD support"
+       depends on SPI_FPGA_MTD && SPI_FPGA_INTEL
+       help
+         Enable the MTD support for the Cyclone 10 FPGA
+         If unsure, say N
+
+endif
+
+endmenu # menu "SPI FPGA Support"
diff --git a/drivers/mtd/fpga/Makefile b/drivers/mtd/fpga/Makefile
new file mode 100644
index 0000000000..2cf19fc7cf
--- /dev/null
+++ b/drivers/mtd/fpga/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2006
+# Wolfgang Denk, DENX Software Engineering, w...@denx.de.
+
+obj-$(CONFIG_SPI_FPGA_CYCLONE10) += cyclone_10.o
diff --git a/drivers/mtd/fpga/cyclone_10.c b/drivers/mtd/fpga/cyclone_10.c
new file mode 100644
index 0000000000..41e273211e
--- /dev/null
+++ b/drivers/mtd/fpga/cyclone_10.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MTD Driver for Passive Serial configuration of Cyclone 10
+ *
+ * Copyright (C) 2020 Bombardier Transportation
+ * Ulf Samuelsson <ext.ulf.samuels...@rail.bombardier.com>
+ * Ulf Samuelsson <u...@emagii.com>
+ *
+ */
+
+#include <common.h>
+#include <console.h>
+#include <dm.h>
+#include <errno.h>
+#include <fdt_support.h>
+//#include <flash.h>
+#include <mtd.h>
+#include <malloc.h>
+#include <spi.h>
+#include <asm/io.h>
+#include <asm/gpio.h>
+#include <linux/delay.h>
+#include <dm/device_compat.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/*
+ * How many milliseconds from CONF_DONE high to enter user mode
+ * Datasheet says 650 us, Delay 2 ms to be safe...
+ */
+#define        USER_MODE_DELAY                         2
+
+struct cyc10_plat {
+       struct udevice          *dev;
+       struct spi_slave        *spi;
+       char                    name[8];
+       struct gpio_desc        nconfig;
+       struct gpio_desc        nstatus;
+       struct gpio_desc        conf_done;
+       struct gpio_desc        crc_error;
+       u32                     cs;
+       int                     flags;
+       int                     config_size;
+};
+
+static inline void write_nCONFIG(struct cyc10_plat *fpga, int value)
+{
+       dm_gpio_set_value(&fpga->nconfig, value);
+}
+
+static inline int read_nSTATUS(struct cyc10_plat *fpga)
+{
+       int val = dm_gpio_get_value(&fpga->nstatus);
+       if (val < 0) {
+               printf("%s: Failure reading nSTATUS; error=%d\n", fpga->name, 
val);
+       }
+       return val;
+}
+
+static inline int read_CONFIG_DONE(struct cyc10_plat *fpga)
+{
+       int val = dm_gpio_get_value(&fpga->conf_done);
+       if (val < 0) {
+               printf("%s: Failure reading CONFIG_DONE; error=%d\n", 
fpga->name, val);
+       }
+       return val;
+}
+
+static inline int read_CRC_ERROR(struct cyc10_plat *fpga)
+{
+       int val = dm_gpio_get_value(&fpga->crc_error);
+       if (val < 0) {
+               printf("%s: Failure reading CRC_ERROR; error=%d\n", fpga->name, 
val);
+       }
+       return val;
+}
+
+/*
+ * Service routine to read status register until ready, or timeout occurs.
+ * Returns non-zero if error.
+ */
+static int cyc10_wait_until_conf_done(struct cyc10_plat *fpga)
+{
+       unsigned long timebase;
+       timebase = get_timer(0);
+
+       while (get_timer(timebase) < USER_MODE_DELAY) {
+               if (read_nSTATUS(fpga) == 0)            /* Bad configuration */
+                       return -EIO;
+               if (read_CONFIG_DONE(fpga) == 1)        /* Ready */
+                       return 0;
+       }
+
+       dev_err(fpga->dev, "fpga configuration timeout\n");
+       return -ETIMEDOUT;
+}
+
+
+/* FPGA Passive Serial configuration is not erasable */
+static int cyc10_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+       instr->state = MTD_ERASE_DONE;
+       return 0;
+}
+
+/* FPGA Passive Serial configuration is not readable, do dummy read */
+static int cyc10_read(struct mtd_info *mtd, loff_t from, size_t len,
+                           size_t *retlen, u_char *buf)
+{
+       for (int i = 0 ; i < len ; i++)
+               buf[i] = 0xFF;
+       *retlen = len;
+       return 0;
+}
+
+static int cyc10_write(struct mtd_info *mtd, loff_t to, size_t len,
+                            size_t *retlen, const u_char *buf)
+{
+       struct udevice *dev = mtd->dev;
+       struct spi_slave *slave = dev_get_parent_priv(dev);
+       struct cyc10_plat *fpga = dev_get_plat(dev);
+       int ret;
+
+       debug("%s: Claiming SPI Bus\n", fpga->name);
+       ret = spi_claim_bus(slave);
+       if (ret) {
+               printf("Failed to claim SPI bus\n");
+               return ret;
+       }
+
+       debug("%s: Starting configuration\n", fpga->name);
+       /* The FPGA configuration start by a positive edge on nCONFIG */
+       write_nCONFIG(fpga, 0);
+       udelay(50);     // Should be low for at least 40 us according to spec.
+       debug("nSTATUS = %d\n", read_nSTATUS(fpga));
+       write_nCONFIG(fpga, 1);
+       /* The FPGA is ready when nSTATUS is high */
+       {
+               int timeout = 200;
+               while(1) {
+                       if (read_nSTATUS(fpga) != 0) {
+                               break;
+                       }
+                       udelay(10);
+                       timeout -= 10;
+                       if (timeout < 0) {
+                               debug("nSTATUS remains low\n");
+                               return -ETIMEDOUT;
+                       }
+                       debug("nSTATUS = %d\n", read_nSTATUS(fpga));
+               }
+       }
+
+       /* FPGA needs some additional clocks to start up, add 8 bits */
+       /* This means we read past the buffer, which is hopefully OK */
+       debug("%s: Writing %d bytes\n", fpga->name, len);
+       ret = spi_xfer(slave, (len * 8) + 8, buf, NULL,
+                      SPI_XFER_ONCE | SPI_LSB_FIRST);
+
+       debug("%s: Releasing SPI Bus\n", fpga->name);
+       spi_release_bus(slave);
+
+       debug("%s: Waiting for CONF_DONE\n", fpga->name);
+
+       ret = cyc10_wait_until_conf_done(fpga);
+       *retlen = len;
+       return ret;
+}
+
+static void cyc10_sync(struct mtd_info *mtd)
+{
+}
+
+static int cyc10_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
+{
+       return 0;
+}
+
+static int cyc10_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
+{
+       return 0;
+}
+
+static int pin_request(struct udevice *dev, const char *pin_name, struct 
gpio_desc *pin, int mode)
+{
+       int     ret;
+       debug("gpio request: %s", pin_name);
+       if (gpio_request_by_name(dev, pin_name, 0, pin, mode )) {
+               debug("FAIL");
+               ret = -EINVAL;
+       } else {
+               ret = 0;
+               debug("OK");
+               debug("%s:%s: %s:[%d]\n", __func__, pin_name, pin->dev->name, 
pin->offset);
+       }
+       return ret;
+}
+
+static int cyc10_probe(struct udevice *dev)
+{
+       struct spi_slave *slave = dev_get_parent_priv(dev);
+       struct dm_spi_slave_plat *plat = dev_get_parent_plat(dev);
+       struct cyc10_plat *fpga = dev_get_plat(dev);
+       struct mtd_info *mtd;
+       int ret = 0;
+       const void *blob = gd->fdt_blob;
+       int node = dev_of_offset(dev);
+       /*
+        * Use "fpga" property as device name if it exists.
+        * Otherwise, use "fpga#" as device name # € {0..3}«»©“”nµªßðđŋħ
+        */
+       const unsigned char *name = fdtdec_locate_byte_array(blob, node, 
"fpga", 4);
+       if (name == NULL) {
+               for (int i = 0; i < 3; i++) {
+                       sprintf(fpga->name, "fpga%d", i);
+                       mtd = get_mtd_device_nm(fpga->name);
+                       if (PTR_ERR(mtd) == -ENODEV) {
+                               goto allocate;
+                       }
+               }
+               printf("Cannot allocate FPGA device\n");
+               return -ENODEV;
+       } else {
+               strncpy(fpga->name, (char *) name, sizeof(fpga->name));
+       }
+
+allocate:
+       fpga->spi = slave;
+       fpga->dev = dev;
+       fpga->cs  = plat->cs;
+
+       debug("%s: slave=%p, cs=%d\n", __func__, slave, fpga->cs);
+       fpga->config_size = fdtdec_get_int(blob, node, "config-size", 0);
+       debug("%s:config-size: %s:[%d]\n", __func__, fpga->name, 
fpga->config_size);
+
+       ret  = pin_request(dev, "nconfig-gpios",        &fpga->nconfig,   
GPIOD_IS_OUT);
+       ret |= pin_request(dev, "nstat-gpios",          &fpga->nstatus,   
GPIOD_IS_IN);
+       ret |= pin_request(dev, "confd-gpios",          &fpga->conf_done, 
GPIOD_IS_IN);
+       if (ret != 0)
+               return ret;
+       ret  = pin_request(dev, "crc-error-gpios",      &fpga->crc_error, 
GPIOD_IS_IN); // May fail.
+
+       mtd = dev_get_uclass_priv(dev);
+       mtd->dev = dev;
+       mtd->name               = fpga->name;
+       mtd->type               = MTD_FPGA;
+       mtd->flags              = MTD_WRITEABLE;
+       mtd->size               = fpga->config_size;
+       mtd->writesize          = fpga->config_size;
+       mtd->writebufsize       = mtd->writesize;
+       mtd->_erase             = cyc10_erase;
+       mtd->_read              = cyc10_read;
+       mtd->_write             = cyc10_write;
+       mtd->_sync              = cyc10_sync;
+       mtd->_lock              = cyc10_lock;
+       mtd->_unlock            = cyc10_unlock;
+       mtd->numeraseregions = 0;
+       mtd->erasesize = 0x10000;
+       if (add_mtd_device(mtd)) {
+               debug("%s: registering %s as MTD failed\n", __func__, 
fpga->name);
+               return -ENOMEM;
+       }
+       return 0;
+}
+
+static const struct udevice_id cyc10_ids[] = {
+       { .compatible = "intel,cyclone10" },
+       {}
+};
+
+U_BOOT_DRIVER(cyc10) = {
+       .name   = "cyc10",
+       .id     = UCLASS_MTD,
+       .of_match = cyc10_ids,
+//     .priv_auto_alloc_size   = sizeof(struct cyc10_priv),
+       .plat_auto = sizeof(struct cyc10_plat),
+       .probe  = cyc10_probe,
+};
-- 
2.17.1

Reply via email to