[PATCH v4 2/5] Firmware: provide a handler to program Altera FPGAs

Steffen Trumtrar s.trumtrar at pengutronix.de
Mon Sep 8 02:29:08 PDT 2014


From: Juergen Beisert <jbe at pengutronix.de>

This handler uses a regular SPI master and a few GPIOs to program an
Altera FPGA in serial mode.

Signed-off-by: Juergen Beisert <jbe at pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
Signed-off-by: Steffen Trumtrar <s.trumtrar at pengutronix.de>
---

Notes:
    Changes since v3:
	- fix some typos
	- use USECOND instead of 1000
	- replace printf with dev_dbg

 drivers/Kconfig                  |   1 +
 drivers/Makefile                 |   1 +
 drivers/firmware/Kconfig         |  11 ++
 drivers/firmware/Makefile        |   1 +
 drivers/firmware/altera_serial.c | 315 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 329 insertions(+)
 create mode 100644 drivers/firmware/Kconfig
 create mode 100644 drivers/firmware/Makefile
 create mode 100644 drivers/firmware/altera_serial.c

diff --git a/drivers/Kconfig b/drivers/Kconfig
index d38032c0e606..e126f62c8f93 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -29,5 +29,6 @@ source "drivers/regulator/Kconfig"
 source "drivers/reset/Kconfig"
 source "drivers/pci/Kconfig"
 source "drivers/rtc/Kconfig"
+source "drivers/firmware/Kconfig"
 
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 4591f9a4086f..cf42190b7bef 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -28,3 +28,4 @@ obj-$(CONFIG_REGULATOR) += regulator/
 obj-$(CONFIG_RESET_CONTROLLER) += reset/
 obj-$(CONFIG_PCI) += pci/
 obj-y += rtc/
+obj-$(CONFIG_FIRMWARE) += firmware/
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
new file mode 100644
index 000000000000..28a173b63f2a
--- /dev/null
+++ b/drivers/firmware/Kconfig
@@ -0,0 +1,11 @@
+menu "Firmware Drivers"
+
+config FIRMWARE_ALTERA_SERIAL
+	bool "Altera SPI programming"
+	depends on OFDEVICE
+	select FIRMWARE
+	help
+	  Programming an Altera FPGA via a few GPIOs for the control lines and
+	  MOSI, MISO and clock from an SPI interface for the data lines
+
+endmenu
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
new file mode 100644
index 000000000000..ec6a5a17083d
--- /dev/null
+++ b/drivers/firmware/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_FIRMWARE_ALTERA_SERIAL) += altera_serial.o
diff --git a/drivers/firmware/altera_serial.c b/drivers/firmware/altera_serial.c
new file mode 100644
index 000000000000..23ba3b00a478
--- /dev/null
+++ b/drivers/firmware/altera_serial.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2013 Juergen Beisert <kernel at pengutronix.de>, Pengutronix
+ *
+ * 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 published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <driver.h>
+#include <firmware.h>
+#include <of_gpio.h>
+#include <xfuncs.h>
+#include <malloc.h>
+#include <gpio.h>
+#include <clock.h>
+#include <spi/spi.h>
+
+#include <fcntl.h>
+#include <fs.h>
+
+/*
+ * Physical requirements:
+ * - three free GPIOs for the signals nCONFIG, CONFIGURE_DONE, nSTATUS
+ * - 32 bit per word, LSB first capable SPI master (MOSI + clock)
+ *
+ * Example how to configure this driver via device tree
+ *
+ *	fpga at 0 {
+ *		compatible = "altr,fpga-passive-serial";
+ *		nstat-gpio = <&gpio4 18 0>;
+ *		confd-gpio = <&gpio4 19 0>;
+ *		nconfig-gpio = <&gpio4 20 0>;
+ *		spi-max-frequency = <10000000>;
+ *		reg = <0>;
+ *	};
+ */
+
+struct fpga_spi {
+	struct firmware_handler fh;
+	int nstat_gpio; /* input GPIO to read the status line */
+	int confd_gpio; /* input GPIO to read the config done line */
+	int nconfig_gpio; /* output GPIO to start the FPGA's config */
+	struct device_d *dev;
+	struct spi_device *spi;
+	bool padding_done;
+};
+
+static int altera_spi_open(struct firmware_handler *fh)
+{
+	struct fpga_spi *this = container_of(fh, struct fpga_spi, fh);
+	struct device_d *dev = this->dev;
+	int ret;
+
+	dev_dbg(dev, "Initiating programming\n");
+
+	/* initiate an FPGA programming */
+	gpio_set_value(this->nconfig_gpio, 0);
+
+	/*
+	 * after about 2 µs the FPGA must acknowledge with
+	 * STATUS and CONFIG DONE lines at low level
+	 */
+	ret = wait_on_timeout(2 * USECOND,
+				(gpio_get_value(this->nstat_gpio) == 0) &&
+				(gpio_get_value(this->confd_gpio) == 0));
+
+	if (ret != 0) {
+		dev_err(dev, "FPGA does not acknowledge the programming initiation\n");
+		if (gpio_get_value(this->nstat_gpio))
+			dev_err(dev, "STATUS is still high!\n");
+		if (gpio_get_value(this->confd_gpio))
+			dev_err(dev, "CONFIG DONE is still high!\n");
+		return ret;
+	}
+
+	/* arm the FPGA to await its new firmware */
+	ret = gpio_set_value(this->nconfig_gpio, 1);
+	if (ret)
+		return ret;
+
+	/* once again, we might need padding the data */
+	this->padding_done = false;
+
+	/*
+	 * after about 1506 µs the FPGA must acknowledge this step
+	 * with the STATUS line at high level
+	 */
+	ret = wait_on_timeout(1600 * USECOND,
+				gpio_get_value(this->nstat_gpio) == 1);
+	if (ret != 0) {
+		dev_err(dev, "FPGA does not acknowledge the programming start\n");
+		return ret;
+	}
+
+	dev_dbg(dev, "Initiating passed\n");
+	/* at the end, wait at least 2 µs prior beginning writing data */
+	udelay(2);
+
+	return 0;
+}
+
+static int altera_spi_write(struct firmware_handler *fh, const void *buf, size_t sz)
+{
+	struct fpga_spi *this = container_of(fh, struct fpga_spi, fh);
+	struct device_d *dev = this->dev;
+	struct spi_transfer t[2];
+	struct spi_message m;
+	u32 dummy;
+	int ret;
+
+	dev_dbg(dev, "Start writing %d bytes.\n", __func__, sz);
+
+	spi_message_init(&m);
+
+	if (sz < sizeof(u32)) {
+		/* simple padding */
+		dummy = 0;
+		memcpy(&dummy, buf, sz);
+		buf = &dummy;
+		sz = sizeof(u32);
+		this->padding_done = true;
+	}
+
+	t[0].tx_buf = buf;
+	t[0].rx_buf = NULL;
+	t[0].len = sz;
+	spi_message_add_tail(&t[0], &m);
+
+	if (sz & 0x3) { /* padding required? */
+		u32 *word_buf = (u32 *)buf;
+		dummy = 0;
+		memcpy(&dummy, &word_buf[sz >> 2], sz & 0x3);
+		t[0].len &= ~0x03;
+		t[1].tx_buf = &dummy;
+		t[1].rx_buf = NULL;
+		t[1].len = sizeof(u32);
+		spi_message_add_tail(&t[1], &m);
+		this->padding_done = true;
+	}
+
+	ret = spi_sync(this->spi, &m);
+	if (ret != 0)
+		dev_err(dev, "programming failure\n");
+
+	return ret;
+}
+
+static int altera_spi_close(struct firmware_handler *fh)
+{
+	struct fpga_spi *this = container_of(fh, struct fpga_spi, fh);
+	struct device_d *dev = this->dev;
+	struct spi_transfer t;
+	struct spi_message m;
+	u32 dummy = 0;
+	int ret;
+
+	dev_dbg(dev, "Finalize programming\n");
+
+	if (this->padding_done == false) {
+		spi_message_init(&m);
+		t.tx_buf = &dummy;
+		t.rx_buf = NULL;
+		t.len = sizeof(dummy);
+		spi_message_add_tail(&t, &m);
+
+		ret = spi_sync(this->spi, &m);
+		if (ret != 0)
+			dev_err(dev, "programming failure\n");
+	}
+
+	/*
+	 * when programming was successful,
+	 * both status lines should be at high level
+	 */
+	ret = wait_on_timeout(10 * USECOND,
+				(gpio_get_value(this->nstat_gpio) == 1) &&
+				(gpio_get_value(this->confd_gpio) == 1));
+	if (ret == 0) {
+		dev_dbg(dev, "Programming successful\n");
+		return ret;
+	}
+
+	dev_err(dev, "Programming failed due to time out\n");
+	if (gpio_get_value(this->nstat_gpio) == 0)
+		dev_err(dev, "STATUS is still low!\n");
+	if (gpio_get_value(this->confd_gpio) == 0)
+		dev_err(dev, "CONFIG DONE is still low!\n");
+
+	return -EIO;
+}
+
+static int altera_spi_of(struct device_d *dev, struct fpga_spi *this)
+{
+	struct device_node *n = dev->device_node;
+	const char *name;
+	int ret;
+
+	name = "nstat-gpio";
+	this->nstat_gpio = of_get_named_gpio(n, name, 0);
+	if (this->nstat_gpio < 0) {
+		ret = this->nstat_gpio;
+		goto out;
+	}
+
+	name = "confd-gpio";
+	this->confd_gpio = of_get_named_gpio(n, name, 0);
+	if (this->confd_gpio < 0) {
+		ret = this->confd_gpio;
+		goto out;
+	}
+
+	name = "nconfig-gpio";
+	this->nconfig_gpio = of_get_named_gpio(n, name, 0);
+	if (this->nconfig_gpio < 0) {
+		ret = this->nconfig_gpio;
+		goto out;
+	}
+
+	/* init to passive and sane values */
+	ret = gpio_direction_output(this->nconfig_gpio, 1);
+	if (ret)
+		return ret;
+	ret = gpio_direction_input(this->nstat_gpio);
+	if (ret)
+		return ret;
+	ret = gpio_direction_input(this->confd_gpio);
+	if (ret)
+		return ret;
+
+	return 0;
+
+out:
+	dev_err(dev, "Cannot request \"%s\" gpio: %s\n", name, strerror(-ret));
+
+	return ret;
+}
+
+static void altera_spi_init_mode(struct spi_device *spi)
+{
+	spi->bits_per_word = 32;
+	/*
+	 * CPHA = CPOL = 0
+	 * the FPGA expects its firmware data with LSB first
+	 */
+	spi->mode = SPI_MODE_0 | SPI_LSB_FIRST;
+}
+
+static int altera_spi_probe(struct device_d *dev)
+{
+	int rc;
+	struct fpga_spi *this;
+	struct firmware_handler *fh;
+	const char *alias = of_alias_get(dev->device_node);
+	const char *model = NULL;
+
+	dev_dbg(dev, "Probing FPGA firmware programmer\n");
+
+	this = xzalloc(sizeof(*this));
+	fh = &this->fh;
+
+	rc = altera_spi_of(dev, this);
+	if (rc != 0)
+		goto out;
+
+	if (alias)
+		fh->id = xstrdup(alias);
+	else
+		fh->id = xstrdup("altera-fpga");
+
+	fh->open = altera_spi_open;
+	fh->write = altera_spi_write;
+	fh->close = altera_spi_close;
+	of_property_read_string(dev->device_node, "compatible", &model);
+	if (model)
+		fh->model = xstrdup(model);
+	fh->dev = dev;
+
+	this->spi = (struct spi_device *)dev->type_data;
+	altera_spi_init_mode(this->spi);
+	this->dev = dev;
+
+	dev_dbg(dev, "Registering FPGA firmware programmer\n");
+	rc = firmwaremgr_register(fh);
+	if (rc != 0) {
+		free(this);
+		goto out;
+	}
+
+	return 0;
+out:
+	free(fh->id);
+	free(this);
+
+	return rc;
+}
+
+static struct of_device_id altera_spi_id_table[] = {
+	{
+		.compatible = "altr,passive-serial",
+	},
+};
+
+static struct driver_d altera_spi_driver = {
+	.name = "altera-fpga",
+	.of_compatible = DRV_OF_COMPAT(altera_spi_id_table),
+	.probe = altera_spi_probe,
+};
+device_spi_driver(altera_spi_driver);
-- 
2.1.0




More information about the barebox mailing list