From 3a7b7f26a68ee934e3537350e66119b3f6f7998d Mon Sep 17 00:00:00 2001 From: Juergen Beisert Date: Mon, 8 Sep 2014 11:29:08 +0200 Subject: Firmware: provide a handler to program Altera FPGAs This handler uses a regular SPI master and a few GPIOs to program an Altera FPGA in serial mode. Signed-off-by: Juergen Beisert Signed-off-by: Sascha Hauer Signed-off-by: Steffen Trumtrar Signed-off-by: Sascha Hauer --- 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 (limited to 'drivers') diff --git a/drivers/Kconfig b/drivers/Kconfig index d38032c0e6..e126f62c8f 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 4591f9a408..cf42190b7b 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 0000000000..28a173b63f --- /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 0000000000..ec6a5a1708 --- /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 0000000000..23ba3b00a4 --- /dev/null +++ b/drivers/firmware/altera_serial.c @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2013 Juergen Beisert , 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * 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@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); -- cgit v1.2.3