diff options
Diffstat (limited to 'drivers/fpga')
-rw-r--r-- | drivers/fpga/Kconfig | 20 | ||||
-rw-r--r-- | drivers/fpga/Makefile | 2 | ||||
-rw-r--r-- | drivers/fpga/altera-cvp.c | 500 | ||||
-rw-r--r-- | drivers/fpga/altera-hps2fpga.c | 12 | ||||
-rw-r--r-- | drivers/fpga/altera-ps-spi.c | 308 | ||||
-rw-r--r-- | drivers/fpga/fpga-region.c | 4 |
6 files changed, 835 insertions, 11 deletions
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 161ba9dccede..ad5448f718b3 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -2,9 +2,7 @@ # FPGA framework configuration # -menu "FPGA Configuration Support" - -config FPGA +menuconfig FPGA tristate "FPGA Configuration Framework" help Say Y here if you want support for configuring FPGAs from the @@ -26,6 +24,20 @@ config FPGA_MGR_ICE40_SPI help FPGA manager driver support for Lattice iCE40 FPGAs over SPI. +config FPGA_MGR_ALTERA_CVP + tristate "Altera Arria-V/Cyclone-V/Stratix-V CvP FPGA Manager" + depends on PCI + help + FPGA manager driver support for Arria-V, Cyclone-V, Stratix-V + and Arria 10 Altera FPGAs using the CvP interface over PCIe. + +config FPGA_MGR_ALTERA_PS_SPI + tristate "Altera FPGA Passive Serial over SPI" + depends on SPI + help + FPGA manager driver support for Altera Arria/Cyclone/Stratix + using the passive serial interface over SPI. + config FPGA_MGR_SOCFPGA tristate "Altera SOCFPGA FPGA Manager" depends on ARCH_SOCFPGA || COMPILE_TEST @@ -106,5 +118,3 @@ config XILINX_PR_DECOUPLER being reprogrammed during partial reconfig. endif # FPGA - -endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 2a4f0218145c..e09895f0525b 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -6,6 +6,8 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers +obj-$(CONFIG_FPGA_MGR_ALTERA_CVP) += altera-cvp.o +obj-$(CONFIG_FPGA_MGR_ALTERA_PS_SPI) += altera-ps-spi.o obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o diff --git a/drivers/fpga/altera-cvp.c b/drivers/fpga/altera-cvp.c new file mode 100644 index 000000000000..08629ee69d11 --- /dev/null +++ b/drivers/fpga/altera-cvp.c @@ -0,0 +1,500 @@ +/* + * FPGA Manager Driver for Altera Arria/Cyclone/Stratix CvP + * + * Copyright (C) 2017 DENX Software Engineering + * + * Anatolij Gustschin <agust@denx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * 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. + * + * Manage Altera FPGA firmware using PCIe CvP. + * Firmware must be in binary "rbf" format. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sizes.h> + +#define CVP_BAR 0 /* BAR used for data transfer in memory mode */ +#define CVP_DUMMY_WR 244 /* dummy writes to clear CvP state machine */ +#define TIMEOUT_US 2000 /* CVP STATUS timeout for USERMODE polling */ + +/* Vendor Specific Extended Capability Registers */ +#define VSE_PCIE_EXT_CAP_ID 0x200 +#define VSE_PCIE_EXT_CAP_ID_VAL 0x000b /* 16bit */ + +#define VSE_CVP_STATUS 0x21c /* 32bit */ +#define VSE_CVP_STATUS_CFG_RDY BIT(18) /* CVP_CONFIG_READY */ +#define VSE_CVP_STATUS_CFG_ERR BIT(19) /* CVP_CONFIG_ERROR */ +#define VSE_CVP_STATUS_CVP_EN BIT(20) /* ctrl block is enabling CVP */ +#define VSE_CVP_STATUS_USERMODE BIT(21) /* USERMODE */ +#define VSE_CVP_STATUS_CFG_DONE BIT(23) /* CVP_CONFIG_DONE */ +#define VSE_CVP_STATUS_PLD_CLK_IN_USE BIT(24) /* PLD_CLK_IN_USE */ + +#define VSE_CVP_MODE_CTRL 0x220 /* 32bit */ +#define VSE_CVP_MODE_CTRL_CVP_MODE BIT(0) /* CVP (1) or normal mode (0) */ +#define VSE_CVP_MODE_CTRL_HIP_CLK_SEL BIT(1) /* PMA (1) or fabric clock (0) */ +#define VSE_CVP_MODE_CTRL_NUMCLKS_OFF 8 /* NUMCLKS bits offset */ +#define VSE_CVP_MODE_CTRL_NUMCLKS_MASK GENMASK(15, 8) + +#define VSE_CVP_DATA 0x228 /* 32bit */ +#define VSE_CVP_PROG_CTRL 0x22c /* 32bit */ +#define VSE_CVP_PROG_CTRL_CONFIG BIT(0) +#define VSE_CVP_PROG_CTRL_START_XFER BIT(1) + +#define VSE_UNCOR_ERR_STATUS 0x234 /* 32bit */ +#define VSE_UNCOR_ERR_CVP_CFG_ERR BIT(5) /* CVP_CONFIG_ERROR_LATCHED */ + +#define DRV_NAME "altera-cvp" +#define ALTERA_CVP_MGR_NAME "Altera CvP FPGA Manager" + +/* Optional CvP config error status check for debugging */ +static bool altera_cvp_chkcfg; + +struct altera_cvp_conf { + struct fpga_manager *mgr; + struct pci_dev *pci_dev; + void __iomem *map; + void (*write_data)(struct altera_cvp_conf *, u32); + char mgr_name[64]; + u8 numclks; +}; + +static enum fpga_mgr_states altera_cvp_state(struct fpga_manager *mgr) +{ + struct altera_cvp_conf *conf = mgr->priv; + u32 status; + + pci_read_config_dword(conf->pci_dev, VSE_CVP_STATUS, &status); + + if (status & VSE_CVP_STATUS_CFG_DONE) + return FPGA_MGR_STATE_OPERATING; + + if (status & VSE_CVP_STATUS_CVP_EN) + return FPGA_MGR_STATE_POWER_UP; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static void altera_cvp_write_data_iomem(struct altera_cvp_conf *conf, u32 val) +{ + writel(val, conf->map); +} + +static void altera_cvp_write_data_config(struct altera_cvp_conf *conf, u32 val) +{ + pci_write_config_dword(conf->pci_dev, VSE_CVP_DATA, val); +} + +/* switches between CvP clock and internal clock */ +static void altera_cvp_dummy_write(struct altera_cvp_conf *conf) +{ + unsigned int i; + u32 val; + + /* set 1 CVP clock cycle for every CVP Data Register Write */ + pci_read_config_dword(conf->pci_dev, VSE_CVP_MODE_CTRL, &val); + val &= ~VSE_CVP_MODE_CTRL_NUMCLKS_MASK; + val |= 1 << VSE_CVP_MODE_CTRL_NUMCLKS_OFF; + pci_write_config_dword(conf->pci_dev, VSE_CVP_MODE_CTRL, val); + + for (i = 0; i < CVP_DUMMY_WR; i++) + conf->write_data(conf, 0); /* dummy data, could be any value */ +} + +static int altera_cvp_wait_status(struct altera_cvp_conf *conf, u32 status_mask, + u32 status_val, int timeout_us) +{ + unsigned int retries; + u32 val; + + retries = timeout_us / 10; + if (timeout_us % 10) + retries++; + + do { + pci_read_config_dword(conf->pci_dev, VSE_CVP_STATUS, &val); + if ((val & status_mask) == status_val) + return 0; + + /* use small usleep value to re-check and break early */ + usleep_range(10, 11); + } while (--retries); + + return -ETIMEDOUT; +} + +static int altera_cvp_teardown(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct altera_cvp_conf *conf = mgr->priv; + struct pci_dev *pdev = conf->pci_dev; + int ret; + u32 val; + + /* STEP 12 - reset START_XFER bit */ + pci_read_config_dword(pdev, VSE_CVP_PROG_CTRL, &val); + val &= ~VSE_CVP_PROG_CTRL_START_XFER; + pci_write_config_dword(pdev, VSE_CVP_PROG_CTRL, val); + + /* STEP 13 - reset CVP_CONFIG bit */ + val &= ~VSE_CVP_PROG_CTRL_CONFIG; + pci_write_config_dword(pdev, VSE_CVP_PROG_CTRL, val); + + /* + * STEP 14 + * - set CVP_NUMCLKS to 1 and then issue CVP_DUMMY_WR dummy + * writes to the HIP + */ + altera_cvp_dummy_write(conf); /* from CVP clock to internal clock */ + + /* STEP 15 - poll CVP_CONFIG_READY bit for 0 with 10us timeout */ + ret = altera_cvp_wait_status(conf, VSE_CVP_STATUS_CFG_RDY, 0, 10); + if (ret) + dev_err(&mgr->dev, "CFG_RDY == 0 timeout\n"); + + return ret; +} + +static int altera_cvp_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct altera_cvp_conf *conf = mgr->priv; + struct pci_dev *pdev = conf->pci_dev; + u32 iflags, val; + int ret; + + iflags = info ? info->flags : 0; + + if (iflags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + + /* Determine allowed clock to data ratio */ + if (iflags & FPGA_MGR_COMPRESSED_BITSTREAM) + conf->numclks = 8; /* ratio for all compressed images */ + else if (iflags & FPGA_MGR_ENCRYPTED_BITSTREAM) + conf->numclks = 4; /* for uncompressed and encrypted images */ + else + conf->numclks = 1; /* for uncompressed and unencrypted images */ + + /* STEP 1 - read CVP status and check CVP_EN flag */ + pci_read_config_dword(pdev, VSE_CVP_STATUS, &val); + if (!(val & VSE_CVP_STATUS_CVP_EN)) { + dev_err(&mgr->dev, "CVP mode off: 0x%04x\n", val); + return -ENODEV; + } + + if (val & VSE_CVP_STATUS_CFG_RDY) { + dev_warn(&mgr->dev, "CvP already started, teardown first\n"); + ret = altera_cvp_teardown(mgr, info); + if (ret) + return ret; + } + + /* + * STEP 2 + * - set HIP_CLK_SEL and CVP_MODE (must be set in the order mentioned) + */ + /* switch from fabric to PMA clock */ + pci_read_config_dword(pdev, VSE_CVP_MODE_CTRL, &val); + val |= VSE_CVP_MODE_CTRL_HIP_CLK_SEL; + pci_write_config_dword(pdev, VSE_CVP_MODE_CTRL, val); + + /* set CVP mode */ + pci_read_config_dword(pdev, VSE_CVP_MODE_CTRL, &val); + val |= VSE_CVP_MODE_CTRL_CVP_MODE; + pci_write_config_dword(pdev, VSE_CVP_MODE_CTRL, val); + + /* + * STEP 3 + * - set CVP_NUMCLKS to 1 and issue CVP_DUMMY_WR dummy writes to the HIP + */ + altera_cvp_dummy_write(conf); + + /* STEP 4 - set CVP_CONFIG bit */ + pci_read_config_dword(pdev, VSE_CVP_PROG_CTRL, &val); + /* request control block to begin transfer using CVP */ + val |= VSE_CVP_PROG_CTRL_CONFIG; + pci_write_config_dword(pdev, VSE_CVP_PROG_CTRL, val); + + /* STEP 5 - poll CVP_CONFIG READY for 1 with 10us timeout */ + ret = altera_cvp_wait_status(conf, VSE_CVP_STATUS_CFG_RDY, + VSE_CVP_STATUS_CFG_RDY, 10); + if (ret) { + dev_warn(&mgr->dev, "CFG_RDY == 1 timeout\n"); + return ret; + } + + /* + * STEP 6 + * - set CVP_NUMCLKS to 1 and issue CVP_DUMMY_WR dummy writes to the HIP + */ + altera_cvp_dummy_write(conf); + + /* STEP 7 - set START_XFER */ + pci_read_config_dword(pdev, VSE_CVP_PROG_CTRL, &val); + val |= VSE_CVP_PROG_CTRL_START_XFER; + pci_write_config_dword(pdev, VSE_CVP_PROG_CTRL, val); + + /* STEP 8 - start transfer (set CVP_NUMCLKS for bitstream) */ + pci_read_config_dword(pdev, VSE_CVP_MODE_CTRL, &val); + val &= ~VSE_CVP_MODE_CTRL_NUMCLKS_MASK; + val |= conf->numclks << VSE_CVP_MODE_CTRL_NUMCLKS_OFF; + pci_write_config_dword(pdev, VSE_CVP_MODE_CTRL, val); + + return 0; +} + +static inline int altera_cvp_chk_error(struct fpga_manager *mgr, size_t bytes) +{ + struct altera_cvp_conf *conf = mgr->priv; + u32 val; + + /* STEP 10 (optional) - check CVP_CONFIG_ERROR flag */ + pci_read_config_dword(conf->pci_dev, VSE_CVP_STATUS, &val); + if (val & VSE_CVP_STATUS_CFG_ERR) { + dev_err(&mgr->dev, "CVP_CONFIG_ERROR after %zu bytes!\n", + bytes); + return -EPROTO; + } + return 0; +} + +static int altera_cvp_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct altera_cvp_conf *conf = mgr->priv; + const u32 *data; + size_t done, remaining; + int status = 0; + u32 mask; + + /* STEP 9 - write 32-bit data from RBF file to CVP data register */ + data = (u32 *)buf; + remaining = count; + done = 0; + + while (remaining >= 4) { + conf->write_data(conf, *data++); + done += 4; + remaining -= 4; + + /* + * STEP 10 (optional) and STEP 11 + * - check error flag + * - loop until data transfer completed + * Config images can be huge (more than 40 MiB), so + * only check after a new 4k data block has been written. + * This reduces the number of checks and speeds up the + * configuration process. + */ + if (altera_cvp_chkcfg && !(done % SZ_4K)) { + status = altera_cvp_chk_error(mgr, done); + if (status < 0) + return status; + } + } + + /* write up to 3 trailing bytes, if any */ + mask = BIT(remaining * 8) - 1; + if (mask) + conf->write_data(conf, *data & mask); + + if (altera_cvp_chkcfg) + status = altera_cvp_chk_error(mgr, count); + + return status; +} + +static int altera_cvp_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct altera_cvp_conf *conf = mgr->priv; + struct pci_dev *pdev = conf->pci_dev; + int ret; + u32 mask; + u32 val; + + ret = altera_cvp_teardown(mgr, info); + if (ret) + return ret; + + /* STEP 16 - check CVP_CONFIG_ERROR_LATCHED bit */ + pci_read_config_dword(pdev, VSE_UNCOR_ERR_STATUS, &val); + if (val & VSE_UNCOR_ERR_CVP_CFG_ERR) { + dev_err(&mgr->dev, "detected CVP_CONFIG_ERROR_LATCHED!\n"); + return -EPROTO; + } + + /* STEP 17 - reset CVP_MODE and HIP_CLK_SEL bit */ + pci_read_config_dword(pdev, VSE_CVP_MODE_CTRL, &val); + val &= ~VSE_CVP_MODE_CTRL_HIP_CLK_SEL; + val &= ~VSE_CVP_MODE_CTRL_CVP_MODE; + pci_write_config_dword(pdev, VSE_CVP_MODE_CTRL, val); + + /* STEP 18 - poll PLD_CLK_IN_USE and USER_MODE bits */ + mask = VSE_CVP_STATUS_PLD_CLK_IN_USE | VSE_CVP_STATUS_USERMODE; + ret = altera_cvp_wait_status(conf, mask, mask, TIMEOUT_US); + if (ret) + dev_err(&mgr->dev, "PLD_CLK_IN_USE|USERMODE timeout\n"); + + return ret; +} + +static const struct fpga_manager_ops altera_cvp_ops = { + .state = altera_cvp_state, + .write_init = altera_cvp_write_init, + .write = altera_cvp_write, + .write_complete = altera_cvp_write_complete, +}; + +static ssize_t show_chkcfg(struct device_driver *dev, char *buf) +{ + return snprintf(buf, 3, "%d\n", altera_cvp_chkcfg); +} + +static ssize_t store_chkcfg(struct device_driver *drv, const char *buf, + size_t count) +{ + int ret; + + ret = kstrtobool(buf, &altera_cvp_chkcfg); + if (ret) + return ret; + + return count; +} + +static DRIVER_ATTR(chkcfg, 0600, show_chkcfg, store_chkcfg); + +static int altera_cvp_probe(struct pci_dev *pdev, + const struct pci_device_id *dev_id); +static void altera_cvp_remove(struct pci_dev *pdev); + +#define PCI_VENDOR_ID_ALTERA 0x1172 + +static struct pci_device_id altera_cvp_id_tbl[] = { + { PCI_VDEVICE(ALTERA, PCI_ANY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(pci, altera_cvp_id_tbl); + +static struct pci_driver altera_cvp_driver = { + .name = DRV_NAME, + .id_table = altera_cvp_id_tbl, + .probe = altera_cvp_probe, + .remove = altera_cvp_remove, +}; + +static int altera_cvp_probe(struct pci_dev *pdev, + const struct pci_device_id *dev_id) +{ + struct altera_cvp_conf *conf; + u16 cmd, val; + int ret; + + /* + * First check if this is the expected FPGA device. PCI config + * space access works without enabling the PCI device, memory + * space access is enabled further down. + */ + pci_read_config_word(pdev, VSE_PCIE_EXT_CAP_ID, &val); + if (val != VSE_PCIE_EXT_CAP_ID_VAL) { + dev_err(&pdev->dev, "Wrong EXT_CAP_ID value 0x%x\n", val); + return -ENODEV; + } + + conf = devm_kzalloc(&pdev->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + /* + * Enable memory BAR access. We cannot use pci_enable_device() here + * because it will make the driver unusable with FPGA devices that + * have additional big IOMEM resources (e.g. 4GiB BARs) on 32-bit + * platform. Such BARs will not have an assigned address range and + * pci_enable_device() will fail, complaining about not claimed BAR, + * even if the concerned BAR is not needed for FPGA configuration + * at all. Thus, enable the device via PCI config space command. + */ + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + if (!(cmd & PCI_COMMAND_MEMORY)) { + cmd |= PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + } + + ret = pci_request_region(pdev, CVP_BAR, "CVP"); + if (ret) { + dev_err(&pdev->dev, "Requesting CVP BAR region failed\n"); + goto err_disable; + } + + conf->pci_dev = pdev; + conf->write_data = altera_cvp_write_data_iomem; + + conf->map = pci_iomap(pdev, CVP_BAR, 0); + if (!conf->map) { + dev_warn(&pdev->dev, "Mapping CVP BAR failed\n"); + conf->write_data = altera_cvp_write_data_config; + } + + snprintf(conf->mgr_name, sizeof(conf->mgr_name), "%s @%s", + ALTERA_CVP_MGR_NAME, pci_name(pdev)); + + ret = fpga_mgr_register(&pdev->dev, conf->mgr_name, + &altera_cvp_ops, conf); + if (ret) + goto err_unmap; + + ret = driver_create_file(&altera_cvp_driver.driver, + &driver_attr_chkcfg); + if (ret) { + dev_err(&pdev->dev, "Can't create sysfs chkcfg file\n"); + fpga_mgr_unregister(&pdev->dev); + goto err_unmap; + } + + return 0; + +err_unmap: + pci_iounmap(pdev, conf->map); + pci_release_region(pdev, CVP_BAR); +err_disable: + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + return ret; +} + +static void altera_cvp_remove(struct pci_dev *pdev) +{ + struct fpga_manager *mgr = pci_get_drvdata(pdev); + struct altera_cvp_conf *conf = mgr->priv; + u16 cmd; + + driver_remove_file(&altera_cvp_driver.driver, &driver_attr_chkcfg); + fpga_mgr_unregister(&pdev->dev); + pci_iounmap(pdev, conf->map); + pci_release_region(pdev, CVP_BAR); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); +} + +module_pci_driver(altera_cvp_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>"); +MODULE_DESCRIPTION("Module to load Altera FPGA over CvP"); diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c index 3066b805f2d0..406d2f10741f 100644 --- a/drivers/fpga/altera-hps2fpga.c +++ b/drivers/fpga/altera-hps2fpga.c @@ -66,7 +66,7 @@ static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge) /* The L3 REMAP register is write only, so keep a cached value. */ static unsigned int l3_remap_shadow; -static spinlock_t l3_remap_lock; +static DEFINE_SPINLOCK(l3_remap_lock); static int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv, bool enable) @@ -143,9 +143,15 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev) int ret; of_id = of_match_device(altera_fpga_of_match, dev); + if (!of_id) { + dev_err(dev, "failed to match device\n"); + return -ENODEV; + } + priv = (struct altera_hps2fpga_data *)of_id->data; - priv->bridge_reset = of_reset_control_get_by_index(dev->of_node, 0); + priv->bridge_reset = of_reset_control_get_exclusive_by_index(dev->of_node, + 0); if (IS_ERR(priv->bridge_reset)) { dev_err(dev, "Could not get %s reset control\n", priv->name); return PTR_ERR(priv->bridge_reset); @@ -171,8 +177,6 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev) return -EBUSY; } - spin_lock_init(&l3_remap_lock); - if (!of_property_read_u32(dev->of_node, "bridge-enable", &enable)) { if (enable > 1) { dev_warn(dev, "invalid bridge-enable %u > 1\n", enable); diff --git a/drivers/fpga/altera-ps-spi.c b/drivers/fpga/altera-ps-spi.c new file mode 100644 index 000000000000..14f14efdf0d5 --- /dev/null +++ b/drivers/fpga/altera-ps-spi.c @@ -0,0 +1,308 @@ +/* + * Altera Passive Serial SPI Driver + * + * Copyright (c) 2017 United Western Technologies, Corporation + * + * Joshua Clayton <stillcompiling@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * Manage Altera FPGA firmware that is loaded over SPI using the passive + * serial configuration method. + * Firmware must be in binary "rbf" format. + * Works on Arria 10, Cyclone V and Stratix V. Should work on Cyclone series. + * May work on other Altera FPGAs. + */ + +#include <linux/bitrev.h> +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> +#include <linux/sizes.h> + +enum altera_ps_devtype { + CYCLONE5, + ARRIA10, +}; + +struct altera_ps_data { + enum altera_ps_devtype devtype; + int status_wait_min_us; + int status_wait_max_us; + int t_cfg_us; + int t_st2ck_us; +}; + +struct altera_ps_conf { + struct gpio_desc *config; + struct gpio_desc *confd; + struct gpio_desc *status; + struct spi_device *spi; + const struct altera_ps_data *data; + u32 info_flags; + char mgr_name[64]; +}; + +/* | Arria 10 | Cyclone5 | Stratix5 | + * t_CF2ST0 | [; 600] | [; 600] | [; 600] |ns + * t_CFG | [2;] | [2;] | [2;] |µs + * t_STATUS | [268; 3000] | [268; 1506] | [268; 1506] |µs + * t_CF2ST1 | [; 3000] | [; 1506] | [; 1506] |µs + * t_CF2CK | [3010;] | [1506;] | [1506;] |µs + * t_ST2CK | [10;] | [2;] | [2;] |µs + * t_CD2UM | [175; 830] | [175; 437] | [175; 437] |µs + */ +static struct altera_ps_data c5_data = { + /* these values for Cyclone5 are compatible with Stratix5 */ + .devtype = CYCLONE5, + .status_wait_min_us = 268, + .status_wait_max_us = 1506, + .t_cfg_us = 2, + .t_st2ck_us = 2, +}; + +static struct altera_ps_data a10_data = { + .devtype = ARRIA10, + .status_wait_min_us = 268, /* min(t_STATUS) */ + .status_wait_max_us = 3000, /* max(t_CF2ST1) */ + .t_cfg_us = 2, /* max { min(t_CFG), max(tCF2ST0) } */ + .t_st2ck_us = 10, /* min(t_ST2CK) */ +}; + +static const struct of_device_id of_ef_match[] = { + { .compatible = "altr,fpga-passive-serial", .data = &c5_data }, + { .compatible = "altr,fpga-arria10-passive-serial", .data = &a10_data }, + {} +}; +MODULE_DEVICE_TABLE(of, of_ef_match); + +static enum fpga_mgr_states altera_ps_state(struct fpga_manager *mgr) +{ + struct altera_ps_conf *conf = mgr->priv; + + if (gpiod_get_value_cansleep(conf->status)) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static inline void altera_ps_delay(int delay_us) +{ + if (delay_us > 10) + usleep_range(delay_us, delay_us + 5); + else + udelay(delay_us); +} + +static int altera_ps_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct altera_ps_conf *conf = mgr->priv; + int min, max, waits; + int i; + + conf->info_flags = info->flags; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + + gpiod_set_value_cansleep(conf->config, 1); + + /* wait min reset pulse time */ + altera_ps_delay(conf->data->t_cfg_us); + + if (!gpiod_get_value_cansleep(conf->status)) { + dev_err(&mgr->dev, "Status pin failed to show a reset\n"); + return -EIO; + } + + gpiod_set_value_cansleep(conf->config, 0); + + min = conf->data->status_wait_min_us; + max = conf->data->status_wait_max_us; + waits = max / min; + if (max % min) + waits++; + + /* wait for max { max(t_STATUS), max(t_CF2ST1) } */ + for (i = 0; i < waits; i++) { + usleep_range(min, min + 10); + if (!gpiod_get_value_cansleep(conf->status)) { + /* wait for min(t_ST2CK)*/ + altera_ps_delay(conf->data->t_st2ck_us); + return 0; + } + } + + dev_err(&mgr->dev, "Status pin not ready.\n"); + return -EIO; +} + +static void rev_buf(char *buf, size_t len) +{ + u32 *fw32 = (u32 *)buf; + size_t extra_bytes = (len & 0x03); + const u32 *fw_end = (u32 *)(buf + len - extra_bytes); + + /* set buffer to lsb first */ + while (fw32 < fw_end) { + *fw32 = bitrev8x4(*fw32); + fw32++; + } + + if (extra_bytes) { + buf = (char *)fw_end; + while (extra_bytes) { + *buf = bitrev8(*buf); + buf++; + extra_bytes--; + } + } +} + +static int altera_ps_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct altera_ps_conf *conf = mgr->priv; + const char *fw_data = buf; + const char *fw_data_end = fw_data + count; + + while (fw_data < fw_data_end) { + int ret; + size_t stride = min_t(size_t, fw_data_end - fw_data, SZ_4K); + + if (!(conf->info_flags & FPGA_MGR_BITSTREAM_LSB_FIRST)) + rev_buf((char *)fw_data, stride); + + ret = spi_write(conf->spi, fw_data, stride); + if (ret) { + dev_err(&mgr->dev, "spi error in firmware write: %d\n", + ret); + return ret; + } + fw_data += stride; + } + + return 0; +} + +static int altera_ps_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct altera_ps_conf *conf = mgr->priv; + const char dummy[] = {0}; + int ret; + + if (gpiod_get_value_cansleep(conf->status)) { + dev_err(&mgr->dev, "Error during configuration.\n"); + return -EIO; + } + + if (!IS_ERR(conf->confd)) { + if (!gpiod_get_raw_value_cansleep(conf->confd)) { + dev_err(&mgr->dev, "CONF_DONE is inactive!\n"); + return -EIO; + } + } + + /* + * After CONF_DONE goes high, send two additional falling edges on DCLK + * to begin initialization and enter user mode + */ + ret = spi_write(conf->spi, dummy, 1); + if (ret) { + dev_err(&mgr->dev, "spi error during end sequence: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct fpga_manager_ops altera_ps_ops = { + .state = altera_ps_state, + .write_init = altera_ps_write_init, + .write = altera_ps_write, + .write_complete = altera_ps_write_complete, +}; + +static int altera_ps_probe(struct spi_device *spi) +{ + struct altera_ps_conf *conf; + const struct of_device_id *of_id; + + conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + of_id = of_match_device(of_ef_match, &spi->dev); + if (!of_id) + return -ENODEV; + + conf->data = of_id->data; + conf->spi = spi; + conf->config = devm_gpiod_get(&spi->dev, "nconfig", GPIOD_OUT_HIGH); + if (IS_ERR(conf->config)) { + dev_err(&spi->dev, "Failed to get config gpio: %ld\n", + PTR_ERR(conf->config)); + return PTR_ERR(conf->config); + } + + conf->status = devm_gpiod_get(&spi->dev, "nstat", GPIOD_IN); + if (IS_ERR(conf->status)) { + dev_err(&spi->dev, "Failed to get status gpio: %ld\n", + PTR_ERR(conf->status)); + return PTR_ERR(conf->status); + } + + conf->confd = devm_gpiod_get(&spi->dev, "confd", GPIOD_IN); + if (IS_ERR(conf->confd)) { + dev_warn(&spi->dev, "Not using confd gpio: %ld\n", + PTR_ERR(conf->confd)); + } + + /* Register manager with unique name */ + snprintf(conf->mgr_name, sizeof(conf->mgr_name), "%s %s", + dev_driver_string(&spi->dev), dev_name(&spi->dev)); + + return fpga_mgr_register(&spi->dev, conf->mgr_name, + &altera_ps_ops, conf); +} + +static int altera_ps_remove(struct spi_device *spi) +{ + fpga_mgr_unregister(&spi->dev); + + return 0; +} + +static const struct spi_device_id altera_ps_spi_ids[] = { + {"cyclone-ps-spi", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, altera_ps_spi_ids); + +static struct spi_driver altera_ps_driver = { + .driver = { + .name = "altera-ps-spi", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_ef_match), + }, + .id_table = altera_ps_spi_ids, + .probe = altera_ps_probe, + .remove = altera_ps_remove, +}; + +module_spi_driver(altera_ps_driver) + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Joshua Clayton <stillcompiling@gmail.com>"); +MODULE_DESCRIPTION("Module to load Altera FPGA firmware over SPI"); diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 3b6b2f4182a1..d9ab7c75b14f 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -319,8 +319,8 @@ static int child_regions_with_firmware(struct device_node *overlay) of_node_put(child_region); if (ret) - pr_err("firmware-name not allowed in child FPGA region: %s", - child_region->full_name); + pr_err("firmware-name not allowed in child FPGA region: %pOF", + child_region); return ret; } |