diff options
Diffstat (limited to 'drivers/fpga')
-rw-r--r-- | drivers/fpga/Kconfig | 39 | ||||
-rw-r--r-- | drivers/fpga/Makefile | 9 | ||||
-rw-r--r-- | drivers/fpga/altera-fpga2sdram.c | 180 | ||||
-rw-r--r-- | drivers/fpga/altera-freeze-bridge.c | 273 | ||||
-rw-r--r-- | drivers/fpga/altera-hps2fpga.c | 222 | ||||
-rw-r--r-- | drivers/fpga/fpga-bridge.c | 395 | ||||
-rw-r--r-- | drivers/fpga/fpga-mgr.c | 97 | ||||
-rw-r--r-- | drivers/fpga/fpga-region.c | 603 | ||||
-rw-r--r-- | drivers/fpga/socfpga-a10.c | 557 | ||||
-rw-r--r-- | drivers/fpga/socfpga.c | 7 | ||||
-rw-r--r-- | drivers/fpga/zynq-fpga.c | 56 |
11 files changed, 2376 insertions, 62 deletions
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index cd84934..ce861a2 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -13,12 +13,26 @@ config FPGA if FPGA +config FPGA_REGION + tristate "FPGA Region" + depends on OF && FPGA_BRIDGE + help + FPGA Regions allow loading FPGA images under control of + the Device Tree. + config FPGA_MGR_SOCFPGA tristate "Altera SOCFPGA FPGA Manager" - depends on ARCH_SOCFPGA + depends on ARCH_SOCFPGA || COMPILE_TEST help FPGA manager driver support for Altera SOCFPGA. +config FPGA_MGR_SOCFPGA_A10 + tristate "Altera SoCFPGA Arria10" + depends on ARCH_SOCFPGA || COMPILE_TEST + select REGMAP_MMIO + help + FPGA manager driver support for Altera Arria10 SoCFPGA. + config FPGA_MGR_ZYNQ_FPGA tristate "Xilinx Zynq FPGA" depends on ARCH_ZYNQ || COMPILE_TEST @@ -26,6 +40,29 @@ config FPGA_MGR_ZYNQ_FPGA help FPGA manager driver support for Xilinx Zynq FPGAs. +config FPGA_BRIDGE + tristate "FPGA Bridge Framework" + depends on OF + help + Say Y here if you want to support bridges connected between host + processors and FPGAs or between FPGAs. + +config SOCFPGA_FPGA_BRIDGE + tristate "Altera SoCFPGA FPGA Bridges" + depends on ARCH_SOCFPGA && FPGA_BRIDGE + help + Say Y to enable drivers for FPGA bridges for Altera SOCFPGA + devices. + +config ALTERA_FREEZE_BRIDGE + tristate "Altera FPGA Freeze Bridge" + depends on ARCH_SOCFPGA && FPGA_BRIDGE + help + Say Y to enable drivers for Altera FPGA Freeze bridges. A + freeze bridge is a bridge that exists in the FPGA fabric to + isolate one region of the FPGA from the busses while that + region is being reprogrammed. + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 8d83fc6..8df07bc 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -7,4 +7,13 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o +obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o + +# FPGA Bridge Drivers +obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o +obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o +obj-$(CONFIG_ALTERA_FREEZE_BRIDGE) += altera-freeze-bridge.o + +# High Level Interfaces +obj-$(CONFIG_FPGA_REGION) += fpga-region.o diff --git a/drivers/fpga/altera-fpga2sdram.c b/drivers/fpga/altera-fpga2sdram.c new file mode 100644 index 0000000..d4eeb74 --- /dev/null +++ b/drivers/fpga/altera-fpga2sdram.c @@ -0,0 +1,180 @@ +/* + * FPGA to SDRAM Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This driver manages a bridge between an FPGA and the SDRAM used by the ARM + * host processor system (HPS). + * + * The bridge contains 4 read ports, 4 write ports, and 6 command ports. + * Reconfiguring these ports requires that no SDRAM transactions occur during + * reconfiguration. The code reconfiguring the ports cannot run out of SDRAM + * nor can the FPGA access the SDRAM during reconfiguration. This driver does + * not support reconfiguring the ports. The ports are configured by code + * running out of on chip ram before Linux is started and the configuration + * is passed in a handoff register in the system manager. + * + * This driver supports enabling and disabling of the configured ports, which + * allows for safe reprogramming of the FPGA, assuming that the new FPGA image + * uses the same port configuration. Bridges must be disabled before + * reprogramming the FPGA and re-enabled after the FPGA has been programmed. + */ + +#include <linux/fpga/fpga-bridge.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> + +#define ALT_SDR_CTL_FPGAPORTRST_OFST 0x80 +#define ALT_SDR_CTL_FPGAPORTRST_PORTRSTN_MSK 0x00003fff +#define ALT_SDR_CTL_FPGAPORTRST_RD_SHIFT 0 +#define ALT_SDR_CTL_FPGAPORTRST_WR_SHIFT 4 +#define ALT_SDR_CTL_FPGAPORTRST_CTRL_SHIFT 8 + +/* + * From the Cyclone V HPS Memory Map document: + * These registers are used to store handoff information between the + * preloader and the OS. These 8 registers can be used to store any + * information. The contents of these registers have no impact on + * the state of the HPS hardware. + */ +#define SYSMGR_ISWGRP_HANDOFF3 (0x8C) + +#define F2S_BRIDGE_NAME "fpga2sdram" + +struct alt_fpga2sdram_data { + struct device *dev; + struct regmap *sdrctl; + int mask; +}; + +static int alt_fpga2sdram_enable_show(struct fpga_bridge *bridge) +{ + struct alt_fpga2sdram_data *priv = bridge->priv; + int value; + + regmap_read(priv->sdrctl, ALT_SDR_CTL_FPGAPORTRST_OFST, &value); + + return (value & priv->mask) == priv->mask; +} + +static inline int _alt_fpga2sdram_enable_set(struct alt_fpga2sdram_data *priv, + bool enable) +{ + return regmap_update_bits(priv->sdrctl, ALT_SDR_CTL_FPGAPORTRST_OFST, + priv->mask, enable ? priv->mask : 0); +} + +static int alt_fpga2sdram_enable_set(struct fpga_bridge *bridge, bool enable) +{ + return _alt_fpga2sdram_enable_set(bridge->priv, enable); +} + +struct prop_map { + char *prop_name; + u32 *prop_value; + u32 prop_max; +}; + +static const struct fpga_bridge_ops altera_fpga2sdram_br_ops = { + .enable_set = alt_fpga2sdram_enable_set, + .enable_show = alt_fpga2sdram_enable_show, +}; + +static const struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-fpga2sdram-bridge" }, + {}, +}; + +static int alt_fpga_bridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct alt_fpga2sdram_data *priv; + u32 enable; + struct regmap *sysmgr; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->sdrctl = syscon_regmap_lookup_by_compatible("altr,sdr-ctl"); + if (IS_ERR(priv->sdrctl)) { + dev_err(dev, "regmap for altr,sdr-ctl lookup failed.\n"); + return PTR_ERR(priv->sdrctl); + } + + sysmgr = syscon_regmap_lookup_by_compatible("altr,sys-mgr"); + if (IS_ERR(sysmgr)) { + dev_err(dev, "regmap for altr,sys-mgr lookup failed.\n"); + return PTR_ERR(sysmgr); + } + + /* Get f2s bridge configuration saved in handoff register */ + regmap_read(sysmgr, SYSMGR_ISWGRP_HANDOFF3, &priv->mask); + + ret = fpga_bridge_register(dev, F2S_BRIDGE_NAME, + &altera_fpga2sdram_br_ops, priv); + if (ret) + return ret; + + dev_info(dev, "driver initialized with handoff %08x\n", priv->mask); + + if (!of_property_read_u32(dev->of_node, "bridge-enable", &enable)) { + if (enable > 1) { + dev_warn(dev, "invalid bridge-enable %u > 1\n", enable); + } else { + dev_info(dev, "%s bridge\n", + (enable ? "enabling" : "disabling")); + ret = _alt_fpga2sdram_enable_set(priv, enable); + if (ret) { + fpga_bridge_unregister(&pdev->dev); + return ret; + } + } + } + + return ret; +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + fpga_bridge_unregister(&pdev->dev); + + return 0; +} + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); + +static struct platform_driver altera_fpga_driver = { + .probe = alt_fpga_bridge_probe, + .remove = alt_fpga_bridge_remove, + .driver = { + .name = "altera_fpga2sdram_bridge", + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +module_platform_driver(altera_fpga_driver); + +MODULE_DESCRIPTION("Altera SoCFPGA FPGA to SDRAM Bridge"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c new file mode 100644 index 0000000..8dcd9fb --- /dev/null +++ b/drivers/fpga/altera-freeze-bridge.c @@ -0,0 +1,273 @@ +/* + * FPGA Freeze Bridge Controller + * + * Copyright (C) 2016 Altera Corporation. All rights reserved. + * + * 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. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/fpga/fpga-bridge.h> + +#define FREEZE_CSR_STATUS_OFFSET 0 +#define FREEZE_CSR_CTRL_OFFSET 4 +#define FREEZE_CSR_ILLEGAL_REQ_OFFSET 8 +#define FREEZE_CSR_REG_VERSION 12 + +#define FREEZE_CSR_SUPPORTED_VERSION 2 + +#define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0) +#define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1) + +#define FREEZE_CSR_CTRL_FREEZE_REQ BIT(0) +#define FREEZE_CSR_CTRL_RESET_REQ BIT(1) +#define FREEZE_CSR_CTRL_UNFREEZE_REQ BIT(2) + +#define FREEZE_BRIDGE_NAME "freeze" + +struct altera_freeze_br_data { + struct device *dev; + void __iomem *base_addr; + bool enable; +}; + +/* + * Poll status until status bit is set or we have a timeout. + */ +static int altera_freeze_br_req_ack(struct altera_freeze_br_data *priv, + u32 timeout, u32 req_ack) +{ + struct device *dev = priv->dev; + void __iomem *csr_illegal_req_addr = priv->base_addr + + FREEZE_CSR_ILLEGAL_REQ_OFFSET; + u32 status, illegal, ctrl; + int ret = -ETIMEDOUT; + + do { + illegal = readl(csr_illegal_req_addr); + if (illegal) { + dev_err(dev, "illegal request detected 0x%x", illegal); + + writel(1, csr_illegal_req_addr); + + illegal = readl(csr_illegal_req_addr); + if (illegal) + dev_err(dev, "illegal request not cleared 0x%x", + illegal); + + ret = -EINVAL; + break; + } + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + dev_dbg(dev, "%s %x %x\n", __func__, status, req_ack); + status &= req_ack; + if (status) { + ctrl = readl(priv->base_addr + FREEZE_CSR_CTRL_OFFSET); + dev_dbg(dev, "%s request %x acknowledged %x %x\n", + __func__, req_ack, status, ctrl); + ret = 0; + break; + } + + udelay(1); + } while (timeout--); + + if (ret == -ETIMEDOUT) + dev_err(dev, "%s timeout waiting for 0x%x\n", + __func__, req_ack); + + return ret; +} + +static int altera_freeze_br_do_freeze(struct altera_freeze_br_data *priv, + u32 timeout) +{ + struct device *dev = priv->dev; + void __iomem *csr_ctrl_addr = priv->base_addr + + FREEZE_CSR_CTRL_OFFSET; + u32 status; + int ret; + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + + dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); + + if (status & FREEZE_CSR_STATUS_FREEZE_REQ_DONE) { + dev_dbg(dev, "%s bridge already disabled %d\n", + __func__, status); + return 0; + } else if (!(status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE)) { + dev_err(dev, "%s bridge not enabled %d\n", __func__, status); + return -EINVAL; + } + + writel(FREEZE_CSR_CTRL_FREEZE_REQ, csr_ctrl_addr); + + ret = altera_freeze_br_req_ack(priv, timeout, + FREEZE_CSR_STATUS_FREEZE_REQ_DONE); + + if (ret) + writel(0, csr_ctrl_addr); + else + writel(FREEZE_CSR_CTRL_RESET_REQ, csr_ctrl_addr); + + return ret; +} + +static int altera_freeze_br_do_unfreeze(struct altera_freeze_br_data *priv, + u32 timeout) +{ + struct device *dev = priv->dev; + void __iomem *csr_ctrl_addr = priv->base_addr + + FREEZE_CSR_CTRL_OFFSET; + u32 status; + int ret; + + writel(0, csr_ctrl_addr); + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + + dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); + + if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) { + dev_dbg(dev, "%s bridge already enabled %d\n", + __func__, status); + return 0; + } else if (!(status & FREEZE_CSR_STATUS_FREEZE_REQ_DONE)) { + dev_err(dev, "%s bridge not frozen %d\n", __func__, status); + return -EINVAL; + } + + writel(FREEZE_CSR_CTRL_UNFREEZE_REQ, csr_ctrl_addr); + + ret = altera_freeze_br_req_ack(priv, timeout, + FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE); + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + + dev_dbg(dev, "%s %d %d\n", __func__, status, readl(csr_ctrl_addr)); + + writel(0, csr_ctrl_addr); + + return ret; +} + +/* + * enable = 1 : allow traffic through the bridge + * enable = 0 : disable traffic through the bridge + */ +static int altera_freeze_br_enable_set(struct fpga_bridge *bridge, + bool enable) +{ + struct altera_freeze_br_data *priv = bridge->priv; + struct fpga_image_info *info = bridge->info; + u32 timeout = 0; + int ret; + + if (enable) { + if (info) + timeout = info->enable_timeout_us; + + ret = altera_freeze_br_do_unfreeze(bridge->priv, timeout); + } else { + if (info) + timeout = info->disable_timeout_us; + + ret = altera_freeze_br_do_freeze(bridge->priv, timeout); + } + + if (!ret) + priv->enable = enable; + + return ret; +} + +static int altera_freeze_br_enable_show(struct fpga_bridge *bridge) +{ + struct altera_freeze_br_data *priv = bridge->priv; + + return priv->enable; +} + +static struct fpga_bridge_ops altera_freeze_br_br_ops = { + .enable_set = altera_freeze_br_enable_set, + .enable_show = altera_freeze_br_enable_show, +}; + +static const struct of_device_id altera_freeze_br_of_match[] = { + { .compatible = "altr,freeze-bridge-controller", }, + {}, +}; +MODULE_DEVICE_TABLE(of, altera_freeze_br_of_match); + +static int altera_freeze_br_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct altera_freeze_br_data *priv; + struct resource *res; + u32 status, revision; + + if (!np) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base_addr)) + return PTR_ERR(priv->base_addr); + + status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET); + if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) + priv->enable = 1; + + revision = readl(priv->base_addr + FREEZE_CSR_REG_VERSION); + if (revision != FREEZE_CSR_SUPPORTED_VERSION) + dev_warn(dev, + "%s Freeze Controller unexpected revision %d != %d\n", + __func__, revision, FREEZE_CSR_SUPPORTED_VERSION); + + return fpga_bridge_register(dev, FREEZE_BRIDGE_NAME, + &altera_freeze_br_br_ops, priv); +} + +static int altera_freeze_br_remove(struct platform_device *pdev) +{ + fpga_bridge_unregister(&pdev->dev); + + return 0; +} + +static struct platform_driver altera_freeze_br_driver = { + .probe = altera_freeze_br_probe, + .remove = altera_freeze_br_remove, + .driver = { + .name = "altera_freeze_br", + .of_match_table = of_match_ptr(altera_freeze_br_of_match), + }, +}; + +module_platform_driver(altera_freeze_br_driver); + +MODULE_DESCRIPTION("Altera Freeze Bridge"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c new file mode 100644 index 0000000..4b354c7 --- /dev/null +++ b/drivers/fpga/altera-hps2fpga.c @@ -0,0 +1,222 @@ +/* + * FPGA to/from HPS Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * Includes this patch from the mailing list: + * fpga: altera-hps2fpga: fix HPS2FPGA bridge visibility to L3 masters + * Signed-off-by: Anatolij Gustschin <agust@denx.de> + * + * 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. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This driver manages bridges on a Altera SOCFPGA between the ARM host + * processor system (HPS) and the embedded FPGA. + * + * This driver supports enabling and disabling of the configured ports, which + * allows for safe reprogramming of the FPGA, assuming that the new FPGA image + * uses the same port configuration. Bridges must be disabled before + * reprogramming the FPGA and re-enabled after the FPGA has been programmed. + */ + +#include <linux/clk.h> +#include <linux/fpga/fpga-bridge.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#define ALT_L3_REMAP_OFST 0x0 +#define ALT_L3_REMAP_MPUZERO_MSK 0x00000001 +#define ALT_L3_REMAP_H2F_MSK 0x00000008 +#define ALT_L3_REMAP_LWH2F_MSK 0x00000010 + +#define HPS2FPGA_BRIDGE_NAME "hps2fpga" +#define LWHPS2FPGA_BRIDGE_NAME "lwhps2fpga" +#define FPGA2HPS_BRIDGE_NAME "fpga2hps" + +struct altera_hps2fpga_data { + const char *name; + struct reset_control *bridge_reset; + struct regmap *l3reg; + unsigned int remap_mask; + struct clk *clk; +}; + +static int alt_hps2fpga_enable_show(struct fpga_bridge *bridge) +{ + struct altera_hps2fpga_data *priv = bridge->priv; + + return reset_control_status(priv->bridge_reset); +} + +/* 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 int _alt_hps2fpga_enable_set(struct altera_hps2fpga_data *priv, + bool enable) +{ + unsigned long flags; + int ret; + + /* bring bridge out of reset */ + if (enable) + ret = reset_control_deassert(priv->bridge_reset); + else + ret = reset_control_assert(priv->bridge_reset); + if (ret) + return ret; + + /* Allow bridge to be visible to L3 masters or not */ + if (priv->remap_mask) { + spin_lock_irqsave(&l3_remap_lock, flags); + l3_remap_shadow |= ALT_L3_REMAP_MPUZERO_MSK; + + if (enable) + l3_remap_shadow |= priv->remap_mask; + else + l3_remap_shadow &= ~priv->remap_mask; + + ret = regmap_write(priv->l3reg, ALT_L3_REMAP_OFST, + l3_remap_shadow); + spin_unlock_irqrestore(&l3_remap_lock, flags); + } + + return ret; +} + +static int alt_hps2fpga_enable_set(struct fpga_bridge *bridge, bool enable) +{ + return _alt_hps2fpga_enable_set(bridge->priv, enable); +} + +static const struct fpga_bridge_ops altera_hps2fpga_br_ops = { + .enable_set = alt_hps2fpga_enable_set, + .enable_show = alt_hps2fpga_enable_show, +}; + +static struct altera_hps2fpga_data hps2fpga_data = { + .name = HPS2FPGA_BRIDGE_NAME, + .remap_mask = ALT_L3_REMAP_H2F_MSK, +}; + +static struct altera_hps2fpga_data lwhps2fpga_data = { + .name = LWHPS2FPGA_BRIDGE_NAME, + .remap_mask = ALT_L3_REMAP_LWH2F_MSK, +}; + +static struct altera_hps2fpga_data fpga2hps_data = { + .name = FPGA2HPS_BRIDGE_NAME, +}; + +static const struct of_device_id altera_fpga_of_match[] = { + { .compatible = "altr,socfpga-hps2fpga-bridge", + .data = &hps2fpga_data }, + { .compatible = "altr,socfpga-lwhps2fpga-bridge", + .data = &lwhps2fpga_data }, + { .compatible = "altr,socfpga-fpga2hps-bridge", + .data = &fpga2hps_data }, + {}, +}; + +static int alt_fpga_bridge_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct altera_hps2fpga_data *priv; + const struct of_device_id *of_id; + u32 enable; + int ret; + + of_id = of_match_device(altera_fpga_of_match, dev); + priv = (struct altera_hps2fpga_data *)of_id->data; + + priv->bridge_reset = of_reset_control_get_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); + } + + if (priv->remap_mask) { + priv->l3reg = syscon_regmap_lookup_by_compatible("altr,l3regs"); + if (IS_ERR(priv->l3reg)) { + dev_err(dev, "regmap for altr,l3regs lookup failed\n"); + return PTR_ERR(priv->l3reg); + } + } + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "no clock specified\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + 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); + } else { + dev_info(dev, "%s bridge\n", + (enable ? "enabling" : "disabling")); + + ret = _alt_hps2fpga_enable_set(priv, enable); + if (ret) { + fpga_bridge_unregister(&pdev->dev); + return ret; + } + } + } + + return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops, + priv); +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + struct fpga_bridge *bridge = platform_get_drvdata(pdev); + struct altera_hps2fpga_data *priv = bridge->priv; + + fpga_bridge_unregister(&pdev->dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +MODULE_DEVICE_TABLE(of, altera_fpga_of_match); + +static struct platform_driver alt_fpga_bridge_driver = { + .probe = alt_fpga_bridge_probe, + .remove = alt_fpga_bridge_remove, + .driver = { + .name = "altera_hps2fpga_bridge", + .of_match_table = of_match_ptr(altera_fpga_of_match), + }, +}; + +module_platform_driver(alt_fpga_bridge_driver); + +MODULE_DESCRIPTION("Altera SoCFPGA HPS to FPGA Bridge"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c new file mode 100644 index 0000000..33ee83e --- /dev/null +++ b/drivers/fpga/fpga-bridge.c @@ -0,0 +1,395 @@ +/* + * FPGA Bridge Framework Driver + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/fpga/fpga-bridge.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +static DEFINE_IDA(fpga_bridge_ida); +static struct class *fpga_bridge_class; + +/* Lock for adding/removing bridges to linked lists*/ +spinlock_t bridge_list_lock; + +static int fpga_bridge_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * fpga_bridge_enable - Enable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_enable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "enable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_enable); + +/** + * fpga_bridge_disable - Disable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_disable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "disable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_disable); + +/** + * of_fpga_bridge_get - get an exclusive reference to a fpga bridge + * + * @np: node pointer of a FPGA bridge + * @info: fpga image specific information + * + * Return fpga_bridge struct if successful. + * Return -EBUSY if someone already has a reference to the bridge. + * Return -ENODEV if @np is not a FPGA Bridge. + */ +struct fpga_bridge *of_fpga_bridge_get(struct device_node *np, + struct fpga_image_info *info) + +{ + struct device *dev; + struct fpga_bridge *bridge; + int ret = -ENODEV; + + dev = class_find_device(fpga_bridge_class, NULL, np, + fpga_bridge_of_node_match); + if (!dev) + goto err_dev; + + bridge = to_fpga_bridge(dev); + if (!bridge) + goto err_dev; + + bridge->info = info; + + if (!mutex_trylock(&bridge->mutex)) { + ret = -EBUSY; + goto err_dev; + } + + if (!try_module_get(dev->parent->driver->owner)) + goto err_ll_mod; + + dev_dbg(&bridge->dev, "get\n"); + + return bridge; + +err_ll_mod: + mutex_unlock(&bridge->mutex); +err_dev: + put_device(dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(of_fpga_bridge_get); + +/** + * fpga_bridge_put - release a reference to a bridge + * + * @bridge: FPGA bridge + */ +void fpga_bridge_put(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "put\n"); + + bridge->info = NULL; + module_put(bridge->dev.parent->driver->owner); + mutex_unlock(&bridge->mutex); + put_device(&bridge->dev); +} +EXPORT_SYMBOL_GPL(fpga_bridge_put); + +/** + * fpga_bridges_enable - enable bridges in a list + * @bridge_list: list of FPGA bridges + * + * Enable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_enable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node; + int ret; + + list_for_each(node, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + ret = fpga_bridge_enable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_enable); + +/** + * fpga_bridges_disable - disable bridges in a list + * + * @bridge_list: list of FPGA bridges + * + * Disable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_disable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node; + int ret; + + list_for_each(node, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + ret = fpga_bridge_disable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_disable); + +/** + * fpga_bridges_put - put bridges + * + * @bridge_list: list of FPGA bridges + * + * For each bridge in the list, put the bridge and remove it from the list. + * If list is empty, do nothing. + */ +void fpga_bridges_put(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node, *next; + unsigned long flags; + + list_for_each_safe(node, next, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + + fpga_bridge_put(bridge); + + spin_lock_irqsave(&bridge_list_lock, flags); + list_del(&bridge->node); + spin_unlock_irqrestore(&bridge_list_lock, flags); + } +} +EXPORT_SYMBOL_GPL(fpga_bridges_put); + +/** + * fpga_bridges_get_to_list - get a bridge, add it to a list + * + * @np: node pointer of a FPGA bridge + * @info: fpga image specific information + * @bridge_list: list of FPGA bridges + * + * Get an exclusive reference to the bridge and and it to the list. + * + * Return 0 for success, error code from of_fpga_bridge_get() othewise. + */ +int fpga_bridge_get_to_list(struct device_node *np, + struct fpga_image_info *info, + struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + unsigned long flags; + + bridge = of_fpga_bridge_get(np, info); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + spin_lock_irqsave(&bridge_list_lock, flags); + list_add(&bridge->node, bridge_list); + spin_unlock_irqrestore(&bridge_list_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_get_to_list); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + + return sprintf(buf, "%s\n", bridge->name); +} + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + int enable = 1; + + if (bridge->br_ops && bridge->br_ops->enable_show) + enable = bridge->br_ops->enable_show(bridge); + + return sprintf(buf, "%s\n", enable ? "enabled" : "disabled"); +} + +static DEVICE_ATTR_RO(name); +static DEVICE_ATTR_RO(state); + +static struct attribute *fpga_bridge_attrs[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_bridge); + +/** + * fpga_bridge_register - register a fpga bridge driver + * @dev: FPGA bridge device from pdev + * @name: FPGA bridge name + * @br_ops: pointer to structure of fpga bridge ops + * @priv: FPGA bridge private data + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_register(struct device *dev, const char *name, + const struct fpga_bridge_ops *br_ops, void *priv) +{ + struct fpga_bridge *bridge; + int id, ret = 0; + + if (!name || !strlen(name)) { + dev_err(dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + id = ida_simple_get(&fpga_bridge_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto error_kfree; + } + + mutex_init(&bridge->mutex); + INIT_LIST_HEAD(&bridge->node); + + bridge->name = name; + bridge->br_ops = br_ops; + bridge->priv = priv; + + device_initialize(&bridge->dev); + bridge->dev.class = fpga_bridge_class; + bridge->dev.parent = dev; + bridge->dev.of_node = dev->of_node; + bridge->dev.id = id; + dev_set_drvdata(dev, bridge); + + ret = dev_set_name(&bridge->dev, "br%d", id); + if (ret) + goto error_device; + + ret = device_add(&bridge->dev); + if (ret) + goto error_device; + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + dev_info(bridge->dev.parent, "fpga bridge [%s] registered\n", + bridge->name); + + return 0; + +error_device: + ida_simple_remove(&fpga_bridge_ida, id); +error_kfree: + kfree(bridge); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_bridge_register); + +/** + * fpga_bridge_unregister - unregister a fpga bridge driver + * @dev: FPGA bridge device from pdev + */ +void fpga_bridge_unregister(struct device *dev) +{ + struct fpga_bridge *bridge = dev_get_drvdata(dev); + + /* + * If the low level driver provides a method for putting bridge into + * a desired state upon unregister, do it. + */ + if (bridge->br_ops && bridge->br_ops->fpga_bridge_remove) + bridge->br_ops->fpga_bridge_remove(bridge); + + device_unregister(&bridge->dev); +} +EXPORT_SYMBOL_GPL(fpga_bridge_unregister); + +static void fpga_bridge_dev_release(struct device *dev) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + + ida_simple_remove(&fpga_bridge_ida, bridge->dev.id); + kfree(bridge); +} + +static int __init fpga_bridge_dev_init(void) +{ + spin_lock_init(&bridge_list_lock); + + fpga_bridge_class = class_create(THIS_MODULE, "fpga_bridge"); + if (IS_ERR(fpga_bridge_class)) + return PTR_ERR(fpga_bridge_class); + + fpga_bridge_class->dev_groups = fpga_bridge_groups; + fpga_bridge_class->dev_release = fpga_bridge_dev_release; + + return 0; +} + +static void __exit fpga_bridge_dev_exit(void) +{ + class_destroy(fpga_bridge_class); + ida_destroy(&fpga_bridge_ida); +} + +MODULE_DESCRIPTION("FPGA Bridge Driver"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_bridge_dev_init); +module_exit(fpga_bridge_dev_exit); diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 953dc91..f0a69d3 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -32,19 +32,20 @@ static struct class *fpga_mgr_class; /** * fpga_mgr_buf_load - load fpga from image in buffer * @mgr: fpga manager - * @flags: flags setting fpga confuration modes + * @info: fpga image specific information * @buf: buffer contain fpga image * @count: byte count of buf * * Step the low level fpga manager through the device-specific steps of getting * an FPGA ready to be configured, writing the image to it, then doing whatever * post-configuration steps necessary. This code assumes the caller got the - * mgr pointer from of_fpga_mgr_get() and checked that it is not an error code. + * mgr pointer from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is + * not an error code. * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, - size_t count) +int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, + const char *buf, size_t count) { struct device *dev = &mgr->dev; int ret; @@ -52,10 +53,12 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, /* * Call the low level driver's write_init function. This will do the * device-specific things to get the FPGA into the state where it is - * ready to receive an FPGA image. + * ready to receive an FPGA image. The low level driver only gets to + * see the first initial_header_size bytes in the buffer. */ mgr->state = FPGA_MGR_STATE_WRITE_INIT; - ret = mgr->mops->write_init(mgr, flags, buf, count); + ret = mgr->mops->write_init(mgr, info, buf, + min(mgr->mops->initial_header_size, count)); if (ret) { dev_err(dev, "Error preparing FPGA for writing\n"); mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR; @@ -78,7 +81,7 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, * steps to finish and set the FPGA into operating mode. */ mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE; - ret = mgr->mops->write_complete(mgr, flags); + ret = mgr->mops->write_complete(mgr, info); if (ret) { dev_err(dev, "Error after writing image data to FPGA\n"); mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; @@ -93,17 +96,19 @@ EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); /** * fpga_mgr_firmware_load - request firmware and load to fpga * @mgr: fpga manager - * @flags: flags setting fpga confuration modes + * @info: fpga image specific information * @image_name: name of image file on the firmware search path * * Request an FPGA image using the firmware class, then write out to the FPGA. * Update the state before each step to provide info on what step failed if * there is a failure. This code assumes the caller got the mgr pointer - * from of_fpga_mgr_get() and checked that it is not an error code. + * from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is not an error + * code. * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, +int fpga_mgr_firmware_load(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *image_name) { struct device *dev = &mgr->dev; @@ -121,7 +126,7 @@ int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, return ret; } - ret = fpga_mgr_buf_load(mgr, flags, fw->data, fw->size); + ret = fpga_mgr_buf_load(mgr, info, fw->data, fw->size); release_firmware(fw); @@ -181,30 +186,11 @@ static struct attribute *fpga_mgr_attrs[] = { }; ATTRIBUTE_GROUPS(fpga_mgr); -static int fpga_mgr_of_node_match(struct device *dev, const void *data) -{ - return dev->of_node == data; -} - -/** - * of_fpga_mgr_get - get an exclusive reference to a fpga mgr - * @node: device node - * - * Given a device node, get an exclusive reference to a fpga mgr. - * - * Return: fpga manager struct or IS_ERR() condition containing error code. - */ -struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +struct fpga_manager *__fpga_mgr_get(struct device *dev) { struct fpga_manager *mgr; - struct device *dev; int ret = -ENODEV; - dev = class_find_device(fpga_mgr_class, NULL, node, - fpga_mgr_of_node_match); - if (!dev) - return ERR_PTR(-ENODEV); - mgr = to_fpga_manager(dev); if (!mgr) goto err_dev; @@ -226,6 +212,55 @@ err_dev: put_device(dev); return ERR_PTR(ret); } + +static int fpga_mgr_dev_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +/** + * fpga_mgr_get - get an exclusive reference to a fpga mgr + * @dev: parent device that fpga mgr was registered with + * + * Given a device, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *fpga_mgr_get(struct device *dev) +{ + struct device *mgr_dev = class_find_device(fpga_mgr_class, NULL, dev, + fpga_mgr_dev_match); + if (!mgr_dev) + return ERR_PTR(-ENODEV); + + return __fpga_mgr_get(mgr_dev); +} +EXPORT_SYMBOL_GPL(fpga_mgr_get); + +static int fpga_mgr_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * of_fpga_mgr_get - get an exclusive reference to a fpga mgr + * @node: device node + * + * Given a device node, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +{ + struct device *dev; + + dev = class_find_device(fpga_mgr_class, NULL, node, + fpga_mgr_of_node_match); + if (!dev) + return ERR_PTR(-ENODEV); + + return __fpga_mgr_get(dev); +} EXPORT_SYMBOL_GPL(of_fpga_mgr_get); /** diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c new file mode 100644 index 0000000..3222fdb --- /dev/null +++ b/drivers/fpga/fpga-region.c @@ -0,0 +1,603 @@ +/* + * FPGA Region - Device Tree support for FPGA programming under Linux + * + * Copyright (C) 2013-2016 Altera Corporation + * + * 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. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/fpga/fpga-bridge.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +/** + * struct fpga_region - FPGA Region structure + * @dev: FPGA Region device + * @mutex: enforces exclusive reference to region + * @bridge_list: list of FPGA bridges specified in region + * @info: fpga image specific information + */ +struct fpga_region { + struct device dev; + struct mutex mutex; /* for exclusive reference to region */ + struct list_head bridge_list; + struct fpga_image_info *info; +}; + +#define to_fpga_region(d) container_of(d, struct fpga_region, dev) + +static DEFINE_IDA(fpga_region_ida); +static struct class *fpga_region_class; + +static const struct of_device_id fpga_region_of_match[] = { + { .compatible = "fpga-region", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fpga_region_of_match); + +static int fpga_region_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * fpga_region_find - find FPGA region + * @np: device node of FPGA Region + * Caller will need to put_device(®ion->dev) when done. + * Returns FPGA Region struct or NULL + */ +static struct fpga_region *fpga_region_find(struct device_node *np) +{ + struct device *dev; + + dev = class_find_device(fpga_region_class, NULL, np, + fpga_region_of_node_match); + if (!dev) + return NULL; + + return to_fpga_region(dev); +} + +/** + * fpga_region_get - get an exclusive reference to a fpga region + * @region: FPGA Region struct + * + * Caller should call fpga_region_put() when done with region. + * + * Return fpga_region struct if successful. + * Return -EBUSY if someone already has a reference to the region. + * Return -ENODEV if @np is not a FPGA Region. + */ +static struct fpga_region *fpga_region_get(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + + if (!mutex_trylock(®ion->mutex)) { + dev_dbg(dev, "%s: FPGA Region already in use\n", __func__); + return ERR_PTR(-EBUSY); + } + + get_device(dev); + of_node_get(dev->of_node); + if (!try_module_get(dev->parent->driver->owner)) { + of_node_put(dev->of_node); + put_device(dev); + mutex_unlock(®ion->mutex); + return ERR_PTR(-ENODEV); + } + + dev_dbg(®ion->dev, "get\n"); + + return region; +} + +/** + * fpga_region_put - release a reference to a region + * + * @region: FPGA region + */ +static void fpga_region_put(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + + dev_dbg(®ion->dev, "put\n"); + + module_put(dev->parent->driver->owner); + of_node_put(dev->of_node); + put_device(dev); + mutex_unlock(®ion->mutex); +} + +/** + * fpga_region_get_manager - get exclusive reference for FPGA manager + * @region: FPGA region + * + * Get FPGA Manager from "fpga-mgr" property or from ancestor region. + * + * Caller should call fpga_mgr_put() when done with manager. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +static struct fpga_manager *fpga_region_get_manager(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + struct device_node *np = dev->of_node; + struct device_node *mgr_node; + struct fpga_manager *mgr; + + of_node_get(np); + while (np) { + if (of_device_is_compatible(np, "fpga-region")) { + mgr_node = of_parse_phandle(np, "fpga-mgr", 0); + if (mgr_node) { + mgr = of_fpga_mgr_get(mgr_node); + of_node_put(np); + return mgr; + } + } + np = of_get_next_parent(np); + } + of_node_put(np); + + return ERR_PTR(-EINVAL); +} + +/** + * fpga_region_get_bridges - create a list of bridges + * @region: FPGA region + * @overlay: device node of the overlay + * + * Create a list of bridges including the parent bridge and the bridges + * specified by "fpga-bridges" property. Note that the + * fpga_bridges_enable/disable/put functions are all fine with an empty list + * if that happens. + * + * Caller should call fpga_bridges_put(®ion->bridge_list) when + * done with the bridges. + * + * Return 0 for success (even if there are no bridges specified) + * or -EBUSY if any of the bridges are in use. + */ +static int fpga_region_get_bridges(struct fpga_region *region, + struct device_node *overlay) +{ + struct device *dev = ®ion->dev; + struct device_node *region_np = dev->of_node; + struct device_node *br, *np, *parent_br = NULL; + int i, ret; + + /* If parent is a bridge, add to list */ + ret = fpga_bridge_get_to_list(region_np->parent, region->info, + ®ion->bridge_list); + if (ret == -EBUSY) + return ret; + + if (!ret) + parent_br = region_np->parent; + + /* If overlay has a list of bridges, use it. */ + if (of_parse_phandle(overlay, "fpga-bridges", 0)) + np = overlay; + else + np = region_np; + + for (i = 0; ; i++) { + br = of_parse_phandle(np, "fpga-bridges", i); + if (!br) + break; + + /* If parent bridge is in list, skip it. */ + if (br == parent_br) + continue; + + /* If node is a bridge, get it and add to list */ + ret = fpga_bridge_get_to_list(br, region->info, + ®ion->bridge_list); + + /* If any of the bridges are in use, give up */ + if (ret == -EBUSY) { + fpga_bridges_put(®ion->bridge_list); + return -EBUSY; + } + } + + return 0; +} + +/** + * fpga_region_program_fpga - program FPGA + * @region: FPGA region + * @firmware_name: name of FPGA image firmware file + * @overlay: device node of the overlay + * Program an FPGA using information in the device tree. + * Function assumes that there is a firmware-name property. + * Return 0 for success or negative error code. + */ +static int fpga_region_program_fpga(struct fpga_region *region, + const char *firmware_name, + struct device_node *overlay) +{ + struct fpga_manager *mgr; + int ret; + + region = fpga_region_get(region); + if (IS_ERR(region)) { + pr_err("failed to get fpga region\n"); + return PTR_ERR(region); + } + + mgr = fpga_region_get_manager(region); + if (IS_ERR(mgr)) { + pr_err("failed to get fpga region manager\n"); + return PTR_ERR(mgr); + } + + ret = fpga_region_get_bridges(region, overlay); + if (ret) { + pr_err("failed to get fpga region bridges\n"); + goto err_put_mgr; + } + + ret = fpga_bridges_disable(®ion->bridge_list); + if (ret) { + pr_err("failed to disable region bridges\n"); + goto err_put_br; + } + + ret = fpga_mgr_firmware_load(mgr, region->info, firmware_name); + if (ret) { + pr_err("failed to load fpga image\n"); + goto err_put_br; + } + + ret = fpga_bridges_enable(®ion->bridge_list); + if (ret) { + pr_err("failed to enable region bridges\n"); + goto err_put_br; + } + + fpga_mgr_put(mgr); + fpga_region_put(region); + + return 0; + +err_put_br: + fpga_bridges_put(®ion->bridge_list); +err_put_mgr: + fpga_mgr_put(mgr); + fpga_region_put(region); + + return ret; +} + +/** + * child_regions_with_firmware + * @overlay: device node of the overlay + * + * If the overlay adds child FPGA regions, they are not allowed to have + * firmware-name property. + * + * Return 0 for OK or -EINVAL if child FPGA region adds firmware-name. + */ +static int child_regions_with_firmware(struct device_node *overlay) +{ + struct device_node *child_region; + const char *child_firmware_name; + int ret = 0; + + of_node_get(overlay); + + child_region = of_find_matching_node(overlay, fpga_region_of_match); + while (child_region) { + if (!of_property_read_string(child_region, "firmware-name", + &child_firmware_name)) { + ret = -EINVAL; + break; + } + child_region = of_find_matching_node(child_region, + fpga_region_of_match); + } + + of_node_put(child_region); + + if (ret) + pr_err("firmware-name not allowed in child FPGA region: %s", + child_region->full_name); + + return ret; +} + +/** + * fpga_region_notify_pre_apply - pre-apply overlay notification + * + * @region: FPGA region that the overlay was applied to + * @nd: overlay notification data + * + * Called after when an overlay targeted to a FPGA Region is about to be + * applied. Function will check the properties that will be added to the FPGA + * region. If the checks pass, it will program the FPGA. + * + * The checks are: + * The overlay must add either firmware-name or external-fpga-config property + * to the FPGA Region. + * + * firmware-name : program the FPGA + * external-fpga-config : FPGA is already programmed + * + * The overlay can add other FPGA regions, but child FPGA regions cannot have a + * firmware-name property since those regions don't exist yet. + * + * If the overlay that breaks the rules, notifier returns an error and the + * overlay is rejected before it goes into the main tree. + * + * Returns 0 for success or negative error code for failure. + */ +static int fpga_region_notify_pre_apply(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + const char *firmware_name = NULL; + struct fpga_image_info *info; + int ret; + + info = devm_kzalloc(®ion->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + region->info = info; + + /* Reject overlay if child FPGA Regions have firmware-name property */ + ret = child_regions_with_firmware(nd->overlay); + if (ret) + return ret; + + /* Read FPGA region properties from the overlay */ + if (of_property_read_bool(nd->overlay, "partial-fpga-config")) + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + if (of_property_read_bool(nd->overlay, "external-fpga-config")) + info->flags |= FPGA_MGR_EXTERNAL_CONFIG; + + of_property_read_string(nd->overlay, "firmware-name", &firmware_name); + + of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us", + &info->enable_timeout_us); + + of_property_read_u32(nd->overlay, "region-freeze-timeout-us", + &info->disable_timeout_us); + + /* If FPGA was externally programmed, don't specify firmware */ + if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) { + pr_err("error: specified firmware and external-fpga-config"); + return -EINVAL; + } + + /* FPGA is already configured externally. We're done. */ + if (info->flags & FPGA_MGR_EXTERNAL_CONFIG) + return 0; + + /* If we got this far, we should be programming the FPGA */ + if (!firmware_name) { + pr_err("should specify firmware-name or external-fpga-config\n"); + return -EINVAL; + } + + return fpga_region_program_fpga(region, firmware_name, nd->overlay); +} + +/** + * fpga_region_notify_post_remove - post-remove overlay notification + * + * @region: FPGA region that was targeted by the overlay that was removed + * @nd: overlay notification data + * + * Called after an overlay has been removed if the overlay's target was a + * FPGA region. + */ +static void fpga_region_notify_post_remove(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + fpga_bridges_disable(®ion->bridge_list); + fpga_bridges_put(®ion->bridge_list); + devm_kfree(®ion->dev, region->info); + region->info = NULL; +} + +/** + * of_fpga_region_notify - reconfig notifier for dynamic DT changes + * @nb: notifier block + * @action: notifier action + * @arg: reconfig data + * + * This notifier handles programming a FPGA when a "firmware-name" property is + * added to a fpga-region. + * + * Returns NOTIFY_OK or error if FPGA programming fails. + */ +static int of_fpga_region_notify(struct notifier_block *nb, + unsigned long action, void *arg) +{ + struct of_overlay_notify_data *nd = arg; + struct fpga_region *region; + int ret; + + switch (action) { + case OF_OVERLAY_PRE_APPLY: + pr_debug("%s OF_OVERLAY_PRE_APPLY\n", __func__); + break; + case OF_OVERLAY_POST_APPLY: + pr_debug("%s OF_OVERLAY_POST_APPLY\n", __func__); + return NOTIFY_OK; /* not for us */ + case OF_OVERLAY_PRE_REMOVE: + pr_debug("%s OF_OVERLAY_PRE_REMOVE\n", __func__); + return NOTIFY_OK; /* not for us */ + case OF_OVERLAY_POST_REMOVE: + pr_debug("%s OF_OVERLAY_POST_REMOVE\n", __func__); + break; + default: /* should not happen */ + return NOTIFY_OK; + } + + region = fpga_region_find(nd->target); + if (!region) + return NOTIFY_OK; + + ret = 0; + switch (action) { + case OF_OVERLAY_PRE_APPLY: + ret = fpga_region_notify_pre_apply(region, nd); + break; + + case OF_OVERLAY_POST_REMOVE: + fpga_region_notify_post_remove(region, nd); + break; + } + + put_device(®ion->dev); + + if (ret) + return notifier_from_errno(ret); + + return NOTIFY_OK; +} + +static struct notifier_block fpga_region_of_nb = { + .notifier_call = of_fpga_region_notify, +}; + +static int fpga_region_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct fpga_region *region; + int id, ret = 0; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto err_kfree; + } + + mutex_init(®ion->mutex); + INIT_LIST_HEAD(®ion->bridge_list); + + device_initialize(®ion->dev); + region->dev.class = fpga_region_class; + region->dev.parent = dev; + region->dev.of_node = np; + region->dev.id = id; + dev_set_drvdata(dev, region); + + ret = dev_set_name(®ion->dev, "region%d", id); + if (ret) + goto err_remove; + + ret = device_add(®ion->dev); + if (ret) + goto err_remove; + + of_platform_populate(np, fpga_region_of_match, NULL, ®ion->dev); + + dev_info(dev, "FPGA Region probed\n"); + + return 0; + +err_remove: + ida_simple_remove(&fpga_region_ida, id); +err_kfree: + kfree(region); + + return ret; +} + +static int fpga_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = platform_get_drvdata(pdev); + + device_unregister(®ion->dev); + + return 0; +} + +static struct platform_driver fpga_region_driver = { + .probe = fpga_region_probe, + .remove = fpga_region_remove, + .driver = { + .name = "fpga-region", + .of_match_table = of_match_ptr(fpga_region_of_match), + }, +}; + +static void fpga_region_dev_release(struct device *dev) +{ + struct fpga_region *region = to_fpga_region(dev); + + ida_simple_remove(&fpga_region_ida, region->dev.id); + kfree(region); +} + +/** + * fpga_region_init - init function for fpga_region class + * Creates the fpga_region class and registers a reconfig notifier. + */ +static int __init fpga_region_init(void) +{ + int ret; + + fpga_region_class = class_create(THIS_MODULE, "fpga_region"); + if (IS_ERR(fpga_region_class)) + return PTR_ERR(fpga_region_class); + + fpga_region_class->dev_release = fpga_region_dev_release; + + ret = of_overlay_notifier_register(&fpga_region_of_nb); + if (ret) + goto err_class; + + ret = platform_driver_register(&fpga_region_driver); + if (ret) + goto err_plat; + + return 0; + +err_plat: + of_overlay_notifier_unregister(&fpga_region_of_nb); +err_class: + class_destroy(fpga_region_class); + ida_destroy(&fpga_region_ida); + return ret; +} + +static void __exit fpga_region_exit(void) +{ + platform_driver_unregister(&fpga_region_driver); + of_overlay_notifier_unregister(&fpga_region_of_nb); + class_destroy(fpga_region_class); + ida_destroy(&fpga_region_ida); +} + +subsys_initcall(fpga_region_init); +module_exit(fpga_region_exit); + +MODULE_DESCRIPTION("FPGA Region"); +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/socfpga-a10.c b/drivers/fpga/socfpga-a10.c new file mode 100644 index 0000000..f8770af --- /dev/null +++ b/drivers/fpga/socfpga-a10.c @@ -0,0 +1,557 @@ +/* + * FPGA Manager Driver for Altera Arria10 SoCFPGA + * + * Copyright (C) 2015-2016 Altera Corporation + * + * 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. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/regmap.h> + +#define A10_FPGAMGR_DCLKCNT_OFST 0x08 +#define A10_FPGAMGR_DCLKSTAT_OFST 0x0c +#define A10_FPGAMGR_IMGCFG_CTL_00_OFST 0x70 +#define A10_FPGAMGR_IMGCFG_CTL_01_OFST 0x74 +#define A10_FPGAMGR_IMGCFG_CTL_02_OFST 0x78 +#define A10_FPGAMGR_IMGCFG_STAT_OFST 0x80 + +#define A10_FPGAMGR_DCLKSTAT_DCLKDONE BIT(0) + +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NCONFIG BIT(0) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NSTATUS BIT(1) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_CONDONE BIT(2) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NCONFIG BIT(8) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_NSTATUS_OE BIT(16) +#define A10_FPGAMGR_IMGCFG_CTL_00_S2F_CONDONE_OE BIT(24) + +#define A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG BIT(0) +#define A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST BIT(16) +#define A10_FPGAMGR_IMGCFG_CTL_01_S2F_NCE BIT(24) + +#define A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL BIT(0) +#define A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_MASK (BIT(16) | BIT(17)) +#define A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_SHIFT 16 +#define A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH BIT(24) +#define A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH_SHIFT 24 + +#define A10_FPGAMGR_IMGCFG_STAT_F2S_CRC_ERROR BIT(0) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_EARLY_USERMODE BIT(1) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_USERMODE BIT(2) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN BIT(4) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_CONDONE_PIN BIT(6) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_PR_READY BIT(9) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_PR_DONE BIT(10) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_PR_ERROR BIT(11) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_NCONFIG_PIN BIT(12) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_MASK (BIT(16) | BIT(17) | BIT(18)) +#define A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_SHIFT 16 + +/* FPGA CD Ratio Value */ +#define CDRATIO_x1 0x0 +#define CDRATIO_x2 0x1 +#define CDRATIO_x4 0x2 +#define CDRATIO_x8 0x3 + +/* Configuration width 16/32 bit */ +#define CFGWDTH_32 1 +#define CFGWDTH_16 0 + +/* + * struct a10_fpga_priv - private data for fpga manager + * @regmap: regmap for register access + * @fpga_data_addr: iomap for single address data register to FPGA + * @clk: clock + */ +struct a10_fpga_priv { + struct regmap *regmap; + void __iomem *fpga_data_addr; + struct clk *clk; +}; + +static bool socfpga_a10_fpga_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case A10_FPGAMGR_DCLKCNT_OFST: + case A10_FPGAMGR_DCLKSTAT_OFST: + case A10_FPGAMGR_IMGCFG_CTL_00_OFST: + case A10_FPGAMGR_IMGCFG_CTL_01_OFST: + case A10_FPGAMGR_IMGCFG_CTL_02_OFST: + return true; + } + return false; +} + +static bool socfpga_a10_fpga_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case A10_FPGAMGR_DCLKCNT_OFST: + case A10_FPGAMGR_DCLKSTAT_OFST: + case A10_FPGAMGR_IMGCFG_CTL_00_OFST: + case A10_FPGAMGR_IMGCFG_CTL_01_OFST: + case A10_FPGAMGR_IMGCFG_CTL_02_OFST: + case A10_FPGAMGR_IMGCFG_STAT_OFST: + return true; + } + return false; +} + +static const struct regmap_config socfpga_a10_fpga_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .writeable_reg = socfpga_a10_fpga_writeable_reg, + .readable_reg = socfpga_a10_fpga_readable_reg, + .max_register = A10_FPGAMGR_IMGCFG_STAT_OFST, + .cache_type = REGCACHE_NONE, +}; + +/* + * from the register map description of cdratio in imgcfg_ctrl_02: + * Normal Configuration : 32bit Passive Parallel + * Partial Reconfiguration : 16bit Passive Parallel + */ +static void socfpga_a10_fpga_set_cfg_width(struct a10_fpga_priv *priv, + int width) +{ + width <<= A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH_SHIFT; + + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_CFGWIDTH, width); +} + +static void socfpga_a10_fpga_generate_dclks(struct a10_fpga_priv *priv, + u32 count) +{ + u32 val; + + /* Clear any existing DONE status. */ + regmap_write(priv->regmap, A10_FPGAMGR_DCLKSTAT_OFST, + A10_FPGAMGR_DCLKSTAT_DCLKDONE); + + /* Issue the DCLK regmap. */ + regmap_write(priv->regmap, A10_FPGAMGR_DCLKCNT_OFST, count); + + /* wait till the dclkcnt done */ + regmap_read_poll_timeout(priv->regmap, A10_FPGAMGR_DCLKSTAT_OFST, val, + val, 1, 100); + + /* Clear DONE status. */ + regmap_write(priv->regmap, A10_FPGAMGR_DCLKSTAT_OFST, + A10_FPGAMGR_DCLKSTAT_DCLKDONE); +} + +#define RBF_ENCRYPTION_MODE_OFFSET 69 +#define RBF_DECOMPRESS_OFFSET 229 + +static int socfpga_a10_fpga_encrypted(u32 *buf32, size_t buf32_size) +{ + if (buf32_size < RBF_ENCRYPTION_MODE_OFFSET + 1) + return -EINVAL; + + /* Is the bitstream encrypted? */ + return ((buf32[RBF_ENCRYPTION_MODE_OFFSET] >> 2) & 3) != 0; +} + +static int socfpga_a10_fpga_compressed(u32 *buf32, size_t buf32_size) +{ + if (buf32_size < RBF_DECOMPRESS_OFFSET + 1) + return -EINVAL; + + /* Is the bitstream compressed? */ + return !((buf32[RBF_DECOMPRESS_OFFSET] >> 1) & 1); +} + +static unsigned int socfpga_a10_fpga_get_cd_ratio(unsigned int cfg_width, + bool encrypt, bool compress) +{ + unsigned int cd_ratio; + + /* + * cd ratio is dependent on cfg width and whether the bitstream + * is encrypted and/or compressed. + * + * | width | encr. | compr. | cd ratio | + * | 16 | 0 | 0 | 1 | + * | 16 | 0 | 1 | 4 | + * | 16 | 1 | 0 | 2 | + * | 16 | 1 | 1 | 4 | + * | 32 | 0 | 0 | 1 | + * | 32 | 0 | 1 | 8 | + * | 32 | 1 | 0 | 4 | + * | 32 | 1 | 1 | 8 | + */ + if (!compress && !encrypt) + return CDRATIO_x1; + + if (compress) + cd_ratio = CDRATIO_x4; + else + cd_ratio = CDRATIO_x2; + + /* If 32 bit, double the cd ratio by incrementing the field */ + if (cfg_width == CFGWDTH_32) + cd_ratio += 1; + + return cd_ratio; +} + +static int socfpga_a10_fpga_set_cdratio(struct fpga_manager *mgr, + unsigned int cfg_width, + const char *buf, size_t count) +{ + struct a10_fpga_priv *priv = mgr->priv; + unsigned int cd_ratio; + int encrypt, compress; + + encrypt = socfpga_a10_fpga_encrypted((u32 *)buf, count / 4); + if (encrypt < 0) + return -EINVAL; + + compress = socfpga_a10_fpga_compressed((u32 *)buf, count / 4); + if (compress < 0) + return -EINVAL; + + cd_ratio = socfpga_a10_fpga_get_cd_ratio(cfg_width, encrypt, compress); + + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_MASK, + cd_ratio << A10_FPGAMGR_IMGCFG_CTL_02_CDRATIO_SHIFT); + + return 0; +} + +static u32 socfpga_a10_fpga_read_stat(struct a10_fpga_priv *priv) +{ + u32 val; + + regmap_read(priv->regmap, A10_FPGAMGR_IMGCFG_STAT_OFST, &val); + + return val; +} + +static int socfpga_a10_fpga_wait_for_pr_ready(struct a10_fpga_priv *priv) +{ + u32 reg, i; + + for (i = 0; i < 10 ; i++) { + reg = socfpga_a10_fpga_read_stat(priv); + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_ERROR) + return -EINVAL; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_READY) + return 0; + } + + return -ETIMEDOUT; +} + +static int socfpga_a10_fpga_wait_for_pr_done(struct a10_fpga_priv *priv) +{ + u32 reg, i; + + for (i = 0; i < 10 ; i++) { + reg = socfpga_a10_fpga_read_stat(priv); + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_ERROR) + return -EINVAL; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_DONE) + return 0; + } + + return -ETIMEDOUT; +} + +/* Start the FPGA programming by initialize the FPGA Manager */ +static int socfpga_a10_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct a10_fpga_priv *priv = mgr->priv; + unsigned int cfg_width; + u32 msel, stat, mask; + int ret; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) + cfg_width = CFGWDTH_16; + else + return -EINVAL; + + /* Check for passive parallel (msel == 000 or 001) */ + msel = socfpga_a10_fpga_read_stat(priv); + msel &= A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_MASK; + msel >>= A10_FPGAMGR_IMGCFG_STAT_F2S_MSEL_SHIFT; + if ((msel != 0) && (msel != 1)) { + dev_dbg(&mgr->dev, "Fail: invalid msel=%d\n", msel); + return -EINVAL; + } + + /* Make sure no external devices are interfering */ + stat = socfpga_a10_fpga_read_stat(priv); + mask = A10_FPGAMGR_IMGCFG_STAT_F2S_NCONFIG_PIN | + A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN; + if ((stat & mask) != mask) + return -EINVAL; + + /* Set cfg width */ + socfpga_a10_fpga_set_cfg_width(priv, cfg_width); + + /* Determine cd ratio from bitstream header and set cd ratio */ + ret = socfpga_a10_fpga_set_cdratio(mgr, cfg_width, buf, count); + if (ret) + return ret; + + /* + * Clear s2f_nce to enable chip select. Leave pr_request + * unasserted and override disabled. + */ + regmap_write(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG); + + /* Set cfg_ctrl to enable s2f dclk and data */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL, + A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL); + + /* + * Disable overrides not needed for pr. + * s2f_config==1 leaves reset deasseted. + */ + regmap_write(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_00_OFST, + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NCONFIG | + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_NSTATUS | + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NENABLE_CONDONE | + A10_FPGAMGR_IMGCFG_CTL_00_S2F_NCONFIG); + + /* Enable override for data, dclk, nce, and pr_request to CSS */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG, 0); + + /* Send some clocks to clear out any errors */ + socfpga_a10_fpga_generate_dclks(priv, 256); + + /* Assert pr_request */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST); + + /* Provide 2048 DCLKs before starting the config data streaming. */ + socfpga_a10_fpga_generate_dclks(priv, 0x7ff); + + /* Wait for pr_ready */ + return socfpga_a10_fpga_wait_for_pr_ready(priv); +} + +/* + * write data to the FPGA data register + */ +static int socfpga_a10_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct a10_fpga_priv *priv = mgr->priv; + u32 *buffer_32 = (u32 *)buf; + size_t i = 0; + + if (count <= 0) + return -EINVAL; + + /* Write out the complete 32-bit chunks */ + while (count >= sizeof(u32)) { + writel(buffer_32[i++], priv->fpga_data_addr); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks */ + switch (count) { + case 3: + writel(buffer_32[i++] & 0x00ffffff, priv->fpga_data_addr); + break; + case 2: + writel(buffer_32[i++] & 0x0000ffff, priv->fpga_data_addr); + break; + case 1: + writel(buffer_32[i++] & 0x000000ff, priv->fpga_data_addr); + break; + case 0: + break; + default: + /* This will never happen */ + return -EFAULT; + } + + return 0; +} + +static int socfpga_a10_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct a10_fpga_priv *priv = mgr->priv; + u32 reg; + int ret; + + /* Wait for pr_done */ + ret = socfpga_a10_fpga_wait_for_pr_done(priv); + + /* Clear pr_request */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_PR_REQUEST, 0); + + /* Send some clocks to clear out any errors */ + socfpga_a10_fpga_generate_dclks(priv, 256); + + /* Disable s2f dclk and data */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_02_OFST, + A10_FPGAMGR_IMGCFG_CTL_02_EN_CFG_CTRL, 0); + + /* Deassert chip select */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NCE, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NCE); + + /* Disable data, dclk, nce, and pr_request override to CSS */ + regmap_update_bits(priv->regmap, A10_FPGAMGR_IMGCFG_CTL_01_OFST, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG, + A10_FPGAMGR_IMGCFG_CTL_01_S2F_NENABLE_CONFIG); + + /* Return any errors regarding pr_done or pr_error */ + if (ret) + return ret; + + /* Final check */ + reg = socfpga_a10_fpga_read_stat(priv); + + if (((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_USERMODE) == 0) || + ((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_CONDONE_PIN) == 0) || + ((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN) == 0)) { + dev_dbg(&mgr->dev, + "Timeout in final check. Status=%08xf\n", reg); + return -ETIMEDOUT; + } + + return 0; +} + +static enum fpga_mgr_states socfpga_a10_fpga_state(struct fpga_manager *mgr) +{ + struct a10_fpga_priv *priv = mgr->priv; + u32 reg = socfpga_a10_fpga_read_stat(priv); + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_USERMODE) + return FPGA_MGR_STATE_OPERATING; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_PR_READY) + return FPGA_MGR_STATE_WRITE; + + if (reg & A10_FPGAMGR_IMGCFG_STAT_F2S_CRC_ERROR) + return FPGA_MGR_STATE_WRITE_COMPLETE_ERR; + + if ((reg & A10_FPGAMGR_IMGCFG_STAT_F2S_NSTATUS_PIN) == 0) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static const struct fpga_manager_ops socfpga_a10_fpga_mgr_ops = { + .initial_header_size = (RBF_DECOMPRESS_OFFSET + 1) * 4, + .state = socfpga_a10_fpga_state, + .write_init = socfpga_a10_fpga_write_init, + .write = socfpga_a10_fpga_write, + .write_complete = socfpga_a10_fpga_write_complete, +}; + +static int socfpga_a10_fpga_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct a10_fpga_priv *priv; + void __iomem *reg_base; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* First mmio base is for register access */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(reg_base)) + return PTR_ERR(reg_base); + + /* Second mmio base is for writing FPGA image data */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + priv->fpga_data_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->fpga_data_addr)) + return PTR_ERR(priv->fpga_data_addr); + + /* regmap for register access */ + priv->regmap = devm_regmap_init_mmio(dev, reg_base, + &socfpga_a10_fpga_regmap_config); + if (IS_ERR(priv->regmap)) + return -ENODEV; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(dev, "no clock specified\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "could not enable clock\n"); + return -EBUSY; + } + + return fpga_mgr_register(dev, "SoCFPGA Arria10 FPGA Manager", + &socfpga_a10_fpga_mgr_ops, priv); +} + +static int socfpga_a10_fpga_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + struct a10_fpga_priv *priv = mgr->priv; + + fpga_mgr_unregister(&pdev->dev); + clk_disable_unprepare(priv->clk); + + return 0; +} + +static const struct of_device_id socfpga_a10_fpga_of_match[] = { + { .compatible = "altr,socfpga-a10-fpga-mgr", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, socfpga_a10_fpga_of_match); + +static struct platform_driver socfpga_a10_fpga_driver = { + .probe = socfpga_a10_fpga_probe, + .remove = socfpga_a10_fpga_remove, + .driver = { + .name = "socfpga_a10_fpga_manager", + .of_match_table = socfpga_a10_fpga_of_match, + }, +}; + +module_platform_driver(socfpga_a10_fpga_driver); + +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_DESCRIPTION("SoCFPGA Arria10 FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/socfpga.c b/drivers/fpga/socfpga.c index 27d2ff2..b6672e6 100644 --- a/drivers/fpga/socfpga.c +++ b/drivers/fpga/socfpga.c @@ -407,13 +407,14 @@ static int socfpga_fpga_reset(struct fpga_manager *mgr) /* * Prepare the FPGA to receive the configuration data. */ -static int socfpga_fpga_ops_configure_init(struct fpga_manager *mgr, u32 flags, +static int socfpga_fpga_ops_configure_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count) { struct socfpga_fpga_priv *priv = mgr->priv; int ret; - if (flags & FPGA_MGR_PARTIAL_RECONFIG) { + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); return -EINVAL; } @@ -478,7 +479,7 @@ static int socfpga_fpga_ops_configure_write(struct fpga_manager *mgr, } static int socfpga_fpga_ops_configure_complete(struct fpga_manager *mgr, - u32 flags) + struct fpga_image_info *info) { struct socfpga_fpga_priv *priv = mgr->priv; u32 status; diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index c2fb412..1812bf7 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -118,7 +118,6 @@ #define FPGA_RST_NONE_MASK 0x0 struct zynq_fpga_priv { - struct device *dev; int irq; struct clk *clk; @@ -175,7 +174,8 @@ static irqreturn_t zynq_fpga_isr(int irq, void *data) return IRQ_HANDLED; } -static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, +static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count) { struct zynq_fpga_priv *priv; @@ -189,7 +189,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, return err; /* don't globally reset PL if we're doing partial reconfig */ - if (!(flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { /* assert AXI interface resets */ regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET, FPGA_RST_ALL_MASK); @@ -217,7 +217,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, INIT_POLL_DELAY, INIT_POLL_TIMEOUT); if (err) { - dev_err(priv->dev, "Timeout waiting for PCFG_INIT"); + dev_err(&mgr->dev, "Timeout waiting for PCFG_INIT\n"); goto out_err; } @@ -231,7 +231,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, INIT_POLL_DELAY, INIT_POLL_TIMEOUT); if (err) { - dev_err(priv->dev, "Timeout waiting for !PCFG_INIT"); + dev_err(&mgr->dev, "Timeout waiting for !PCFG_INIT\n"); goto out_err; } @@ -245,7 +245,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, INIT_POLL_DELAY, INIT_POLL_TIMEOUT); if (err) { - dev_err(priv->dev, "Timeout waiting for PCFG_INIT"); + dev_err(&mgr->dev, "Timeout waiting for PCFG_INIT\n"); goto out_err; } } @@ -262,7 +262,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, /* check that we have room in the command queue */ status = zynq_fpga_read(priv, STATUS_OFFSET); if (status & STATUS_DMA_Q_F) { - dev_err(priv->dev, "DMA command queue full"); + dev_err(&mgr->dev, "DMA command queue full\n"); err = -EBUSY; goto out_err; } @@ -295,7 +295,8 @@ static int zynq_fpga_ops_write(struct fpga_manager *mgr, in_count = count; priv = mgr->priv; - kbuf = dma_alloc_coherent(priv->dev, count, &dma_addr, GFP_KERNEL); + kbuf = + dma_alloc_coherent(mgr->dev.parent, count, &dma_addr, GFP_KERNEL); if (!kbuf) return -ENOMEM; @@ -331,19 +332,19 @@ static int zynq_fpga_ops_write(struct fpga_manager *mgr, zynq_fpga_write(priv, INT_STS_OFFSET, intr_status); if (!((intr_status & IXR_D_P_DONE_MASK) == IXR_D_P_DONE_MASK)) { - dev_err(priv->dev, "Error configuring FPGA"); + dev_err(&mgr->dev, "Error configuring FPGA\n"); err = -EFAULT; } clk_disable(priv->clk); out_free: - dma_free_coherent(priv->dev, in_count, kbuf, dma_addr); - + dma_free_coherent(mgr->dev.parent, count, kbuf, dma_addr); return err; } -static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, u32 flags) +static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) { struct zynq_fpga_priv *priv = mgr->priv; int err; @@ -364,7 +365,7 @@ static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, u32 flags) return err; /* for the partial reconfig case we didn't touch the level shifters */ - if (!(flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { /* enable level shifters from PL to PS */ regmap_write(priv->slcr, SLCR_LVL_SHFTR_EN_OFFSET, LVL_SHFTR_ENABLE_PL_TO_PS); @@ -416,8 +417,6 @@ static int zynq_fpga_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - priv->dev = dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->io_base = devm_ioremap_resource(dev, res); if (IS_ERR(priv->io_base)) @@ -426,7 +425,7 @@ static int zynq_fpga_probe(struct platform_device *pdev) priv->slcr = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); if (IS_ERR(priv->slcr)) { - dev_err(dev, "unable to get zynq-slcr regmap"); + dev_err(dev, "unable to get zynq-slcr regmap\n"); return PTR_ERR(priv->slcr); } @@ -434,38 +433,41 @@ static int zynq_fpga_probe(struct platform_device *pdev) priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) { - dev_err(dev, "No IRQ available"); + dev_err(dev, "No IRQ available\n"); return priv->irq; } - err = devm_request_irq(dev, priv->irq, zynq_fpga_isr, 0, - dev_name(dev), priv); - if (err) { - dev_err(dev, "unable to request IRQ"); - return err; - } - priv->clk = devm_clk_get(dev, "ref_clk"); if (IS_ERR(priv->clk)) { - dev_err(dev, "input clock not found"); + dev_err(dev, "input clock not found\n"); return PTR_ERR(priv->clk); } err = clk_prepare_enable(priv->clk); if (err) { - dev_err(dev, "unable to enable clock"); + dev_err(dev, "unable to enable clock\n"); return err; } /* unlock the device */ zynq_fpga_write(priv, UNLOCK_OFFSET, UNLOCK_MASK); + zynq_fpga_write(priv, INT_MASK_OFFSET, 0xFFFFFFFF); + zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK); + err = devm_request_irq(dev, priv->irq, zynq_fpga_isr, 0, dev_name(dev), + priv); + if (err) { + dev_err(dev, "unable to request IRQ\n"); + clk_disable_unprepare(priv->clk); + return err; + } + clk_disable(priv->clk); err = fpga_mgr_register(dev, "Xilinx Zynq FPGA Manager", &zynq_fpga_ops, priv); if (err) { - dev_err(dev, "unable to register FPGA manager"); + dev_err(dev, "unable to register FPGA manager\n"); clk_unprepare(priv->clk); return err; } |