[PATCH v15 5/6] fpga: fpga-area and fpga-bus: device tree control for FPGA

From: atull
Date: Wed Jan 20 2016 - 14:41:21 EST


From: Alan Tull <atull@xxxxxxxxxxxxxxxxxxxxx>

FPGA Area and FPGA Bus support programming FPGA under control of
the Device Tree.

A FPGA Bus contains the devices necessary for programming an
FPGA.

When a Device Tree Overlay containing a FPGA Area is
applied, the FPGA Area will be probed and will:
* check to see if there is an image to program to a FPGA
* get references to the FPGA manager and bridges if any
* disable FPGA bridges to prevent spurious data on busses
* reprogram the FPGA
* enable the specified FPGA bridges
* populate the child devices

Removing the Device Tree Overlay is also supported.

This supports fpga use where hardware blocks on a fpga will
need drivers.

Signed-off-by: Alan Tull <atull@xxxxxxxxxxxxxxxxxxxxx>
---
v9: initial version (this patch added during rest of patchset's v9)
v10: request deferral if fpga mgr or bridges not available yet
cleanup as fpga manager core goes into the real kernel
Don't assume bridges are disabled before programming FPGA
Don't hang onto reference for fpga manager
Move to staging/simple-fpga-bus
v11: No change in this patch for v11 of the patch set
v12: Moved out of staging.
Use fpga bridges framework.
v13: If no bridges are specified, assume we don't need any.
Clean up debug messages
Some dev_info -> dev_dbg
Remove unneeded #include
Fix size of array of pointers
Don't need to specify .owner
Use common binding: firmware-name
v14: OK it's not a simple bus. Call it "FPGA Area"
Remove bindings that specify FPGA manager and FPGA bridges
Use parent FPGA bridge and bridges that are its peers
Use ancestor FPGA Manager
v15: Add altr,fpga-bus implementation
Change compatible string "fpga-area" -> "altr,fpga-area"
---
drivers/fpga/Kconfig | 7 +
drivers/fpga/Makefile | 3 +
drivers/fpga/fpga-area.c | 352 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 362 insertions(+)
create mode 100644 drivers/fpga/fpga-area.c

diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index b6cfd89..6ac916b 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -31,6 +31,13 @@ config FPGA_BRIDGE
Say Y here if you want to support bridges connected between host
processors and FPGAs or between FPGAs.

+config FPGA_AREA
+ bool "Device Tree Based FPGA Reprogramming"
+ depends on FPGA_BRIDGE
+ help
+ Enable FPGA Area which supports programming FPGAs under control of
+ Device Tree.
+
endif # FPGA

endmenu
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 4baef00..4f7e49f 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -11,3 +11,6 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o

# FPGA Bridge Drivers
obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o
+
+# High Level Interfaces
+obj-$(CONFIG_FPGA_AREA) += fpga-area.o
diff --git a/drivers/fpga/fpga-area.c b/drivers/fpga/fpga-area.c
new file mode 100644
index 0000000..cec332f
--- /dev/null
+++ b/drivers/fpga/fpga-area.c
@@ -0,0 +1,352 @@
+/*
+ * FPGA Tree Area Support for Device Tree controlled FPGA reprogramming
+ *
+ * Copyright (C) 2013-2015 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/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+
+/*
+ * In the case of a FPGA doing full reconfiguration, the area == the whole
+ * FPGA. In the case of partial reconfiguration, several areas can be
+ * reconfigured separately.
+ */
+
+/**
+ * struct fpga_area
+ * @mgr: FPGA Manager
+ * @flags: Flags for reconfiguration
+ * @firmware_name: Name of FPGA image file
+ * @bridge_list: Linked list of FPGA bridges controlled by area
+ * @br: FPGA Bridge corresponding to area
+ * @bus_np: device node of ancestor FPGA Bus
+ */
+struct fpga_area {
+ struct fpga_manager *mgr;
+ u32 flags;
+ const char *firmware_name;
+ struct list_head bridge_list;
+ struct fpga_bridge *br;
+ struct device_node *bus_np;
+};
+
+/**
+ * fpga_area_get_parent_peer_bridges - get bridges that are peers of parent
+ * @area: FPGA Area struct
+ *
+ * Intended to support case where multiple bridges need to be disabled
+ * during FPGA reprogramming.
+ *
+ * Finds the FPGA bridge that is the parent of @area in the device tree.
+ * Creates a linked list of FPGA bridges that includes the parent bridge and
+ * its peers. Gets an exclusive reference to each of these bridges as they
+ * are added to the list. The list of bridges is saved in @area's
+ * fpga_bridge struct.
+ *
+ * These bridges must be disabled while the FPGA is being reprogrammed to
+ * support the children of the @area bridge and enabled after FPGA
+ * programming is finished.
+ *
+ * For the use case where no FPGA bridges are required, the parent node should
+ * be a FPGA Manager. In this case, the bridge list will end up empty.
+ *
+ * Returns error code or 0 for success. Returns 0 if the parent is a FPGA
+ * manager.
+ */
+static int fpga_area_get_parent_peer_bridges(struct fpga_area *area)
+{
+ struct device_node *parent, *child;
+ struct fpga_bridge *bridge;
+ int ret;
+
+ /* Create a list of bridges that are peers of area's parent */
+ parent = of_get_parent(area->br->dev.of_node);
+ parent = of_get_next_parent(parent);
+
+ for_each_child_of_node(parent, child) {
+ /* If node is a bridge, get it and add to list */
+ ret = fpga_bridge_get_to_list(child, &area->bridge_list);
+
+ /* if any of the bridges are in use, give up */
+ if (ret == -EBUSY) {
+ fpga_bridges_put(&area->bridge_list);
+ of_node_put(parent);
+ return PTR_ERR(bridge);
+ }
+ }
+ of_node_put(parent);
+
+ return 0;
+}
+
+/**
+ * fpga_area_get_bridges - create list of exclusive references to fpga bridges
+ * @area: FPGA Area struct
+ *
+ * Get exclusive references to a FPGA bridge or bridges.
+ * In the case of full reconfiguration build a list of bridges that are the
+ * parent of @area and its peers. We are reprogramming the full FPGA and
+ * need to have no communication on the processor/FPGA bridges while that
+ * is happening
+ * In the case of partial reconfiguration, only add the parent of @area to
+ * the list. This one bridge is a freeze block which is in the FPGA itself
+ * and is downstream from its parent bridge and the parent's peers.
+ *
+ * Return 0 for success. Return -ENODEV is there are no bridges.
+ * Pass other error codes ultimately from of_fpga_bridge_get() such as:
+ * Return -EBUSY if any of the bridges were already gotten.
+ */
+static int fpga_area_get_bridges(struct fpga_area *area)
+{
+ struct device_node *parent;
+ int ret = 0;
+
+ /* If parent is FPGA Manager, no bridges to get */
+ parent = of_get_parent(area->br->dev.of_node);
+ if (parent == area->mgr->dev.of_node) {
+ of_node_put(parent);
+ return -ENODEV;
+ }
+
+ if (area->flags & FPGA_MGR_PARTIAL_RECONFIG)
+ ret = fpga_bridge_get_to_list(parent, &area->bridge_list);
+ else
+ ret = fpga_area_get_parent_peer_bridges(area);
+
+ of_node_put(parent);
+
+ return ret;
+}
+
+/**
+ * fpga_area_load - program the FPGA based on info in area
+ * @area: FPGA Area struct
+ *
+ * Program the FPGA that the area has a reference to.
+ *
+ * Returns 0 for success or error codes passed down from
+ * fpga_mgr_firmware_load()
+ */
+static int fpga_area_load(struct fpga_area *area)
+{
+ return fpga_mgr_firmware_load(area->mgr,
+ area->flags,
+ area->firmware_name);
+}
+
+/**
+ * fpga_area_get_bus - find ancestor FPGA Bus, get a reference
+ * @area: FPGA Area struct
+ *
+ * Returns 0 for success or -ENODEV if not a child of a FPGA Bus.
+ */
+static int fpga_area_get_bus(struct fpga_area *area)
+{
+ struct device_node *np = area->br->dev.of_node;
+
+ of_node_get(np);
+
+ while (np && !of_device_is_compatible(np, "altr,fpga-bus"))
+ np = of_get_next_parent(np);
+
+ if (!np)
+ return -ENODEV;
+
+ area->bus_np = np;
+
+ return 0;
+}
+
+/**
+ * fpga_area_put_bus - put FPGA Bus saved in @area
+ * @area: FPGA Area struct
+ */
+static void fpga_area_put_bus(struct fpga_area *area)
+{
+ of_node_put(area->bus_np);
+ area->bus_np = NULL;
+}
+
+/**
+ * fpga_area_get_manager - get exclusive reference for FPGA Manager
+ * @area: FPGA Area struct
+ *
+ * One of the ancestor nodes of the FPGA Area should be a FPGA Bus. One of
+ * the children of that FPGA Bus should be a FPGA Manager.
+ * Assuming that fpga_area_get_bus() has already found the bus, this function
+ * finds the FPGA Manager and saves it in the area struct.
+ *
+ * Return: 0 for success or IS_ERR() condition containing error code.
+ */
+static int fpga_area_get_manager(struct fpga_area *area)
+{
+ struct device_node *child;
+ struct fpga_manager *mgr;
+
+ for_each_child_of_node(area->bus_np, child) {
+ mgr = of_fpga_mgr_get(child);
+ if (IS_ERR(mgr))
+ continue;
+
+ area->mgr = mgr;
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+/**
+ * fpga_area_put_manager - put exclusive reference to FPGA Manager
+ * @area: FPGA Area struct
+ */
+static void fpga_area_put_manager(struct fpga_area *area)
+{
+ fpga_mgr_put(area->mgr);
+ area->mgr = NULL;
+}
+
+/**
+ * fpga_area_probe - probe function for FPGA area
+ * @pdev: platform device
+ *
+ * If there is an image to program to a FPGA, get the FPGA Manager and bridges,
+ * reprogram the FPGA, and populate the child devices.
+ *
+ * If there are FPGA Bridges, this function will hold the references to the
+ * bridges; they are released in fpga_area_remove().
+ *
+ * Return: 0 for success, -EBUSY if someone already got the bridges or manager.
+ */
+static int fpga_area_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct fpga_area *area;
+ int ret;
+
+ area = devm_kzalloc(dev, sizeof(*area), GFP_KERNEL);
+ if (!area)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&area->bridge_list);
+
+ ret = fpga_bridge_register(dev, "FPGA Area", NULL, area);
+ if (ret)
+ return ret;
+ area->br = dev_get_drvdata(dev);
+
+ if (of_property_read_string(np, "firmware-name",
+ &area->firmware_name)) {
+ of_platform_populate(np, of_default_bus_match_table, NULL, dev);
+ return 0;
+ }
+
+ if (of_property_read_bool(np, "partial-reconfig"))
+ area->flags |= FPGA_MGR_PARTIAL_RECONFIG;
+
+ ret = fpga_area_get_bus(area);
+ if (ret) {
+ dev_dbg(dev, "Should be child of a FPGA Bus");
+ goto err_unreg;
+ }
+
+ ret = fpga_area_get_manager(area);
+ if (ret) {
+ dev_dbg(dev, "Could not find FPGA Manager");
+ goto err_unreg;
+ }
+
+ /* Give up if there is an error other than no bridges. */
+ ret = fpga_area_get_bridges(area);
+ if (ret && ret != -ENODEV)
+ goto err_release;
+
+ ret = fpga_bridges_disable(&area->bridge_list);
+ if (ret)
+ goto err_release;
+
+ ret = fpga_area_load(area);
+ if (ret)
+ goto err_release;
+
+ ret = fpga_bridges_enable(&area->bridge_list);
+ if (ret)
+ goto err_release;
+
+ /* If successful, put the mgr, but keep the bridges */
+ fpga_area_put_manager(area);
+
+ of_platform_populate(np, of_default_bus_match_table, NULL, dev);
+
+ return 0;
+
+err_release:
+ fpga_bridges_put(&area->bridge_list);
+ fpga_area_put_manager(area);
+err_unreg:
+ fpga_bridge_unregister(dev);
+
+ return ret;
+}
+
+/**
+ * fpga_area_remove - remove a FPGA area
+ * @pdev: platform device
+ *
+ * Called when an FPGA Area is removed. If there are any FPGA Bridges in the
+ * area's bridge list, disable them and put them.
+ *
+ * Return: 0
+ */
+static int fpga_area_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fpga_bridge *bridge = dev_get_drvdata(dev);
+ struct fpga_area *area = bridge->priv;
+
+ fpga_area_put_bus(area);
+
+ fpga_bridges_disable(&area->bridge_list);
+ fpga_bridges_put(&area->bridge_list);
+
+ fpga_bridge_unregister(dev);
+
+ return 0;
+}
+
+static const struct of_device_id fpga_area_of_match[] = {
+ { .compatible = "fpga-area", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, fpga_area_of_match);
+
+static struct platform_driver fpga_area_driver = {
+ .probe = fpga_area_probe,
+ .remove = fpga_area_remove,
+ .driver = {
+ .name = "FPGA Area",
+ .of_match_table = of_match_ptr(fpga_area_of_match),
+ },
+};
+
+module_platform_driver(fpga_area_driver);
+
+MODULE_DESCRIPTION("Altera FPGA Bus");
+MODULE_AUTHOR("Alan Tull <atull@xxxxxxxxxxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5