diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/fpga | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/fpga')
35 files changed, 11606 insertions, 0 deletions
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig new file mode 100644 index 000000000..87337fcfb --- /dev/null +++ b/drivers/fpga/Kconfig @@ -0,0 +1,202 @@ +# +# FPGA framework configuration +# + +menuconfig FPGA + tristate "FPGA Configuration Framework" + help + Say Y here if you want support for configuring FPGAs from the + kernel. The FPGA framework adds a FPGA manager class and FPGA + manager drivers. + +if FPGA + +config FPGA_MGR_SOCFPGA + tristate "Altera SOCFPGA FPGA Manager" + 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 ALTERA_PR_IP_CORE + tristate "Altera Partial Reconfiguration IP Core" + help + Core driver support for Altera Partial Reconfiguration IP component + +config ALTERA_PR_IP_CORE_PLAT + tristate "Platform support of Altera Partial Reconfiguration IP Core" + depends on ALTERA_PR_IP_CORE && OF && HAS_IOMEM + help + Platform driver support for Altera Partial Reconfiguration IP + component + +config FPGA_MGR_ALTERA_PS_SPI + tristate "Altera FPGA Passive Serial over SPI" + depends on SPI + select BITREVERSE + help + FPGA manager driver support for Altera Arria/Cyclone/Stratix + using the passive serial interface 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_ZYNQ_FPGA + tristate "Xilinx Zynq FPGA" + depends on ARCH_ZYNQ || COMPILE_TEST + help + FPGA manager driver support for Xilinx Zynq FPGAs. + +config FPGA_MGR_XILINX_SPI + tristate "Xilinx Configuration over Slave Serial (SPI)" + depends on SPI + help + FPGA manager driver support for Xilinx FPGA configuration + over slave serial interface. + +config FPGA_MGR_ICE40_SPI + tristate "Lattice iCE40 SPI" + depends on OF && SPI + help + FPGA manager driver support for Lattice iCE40 FPGAs over SPI. + +config FPGA_MGR_MACHXO2_SPI + tristate "Lattice MachXO2 SPI" + depends on SPI + help + FPGA manager driver support for Lattice MachXO2 configuration + over slave SPI interface. + +config FPGA_MGR_TS73XX + tristate "Technologic Systems TS-73xx SBC FPGA Manager" + depends on ARCH_EP93XX && MACH_TS72XX + help + FPGA manager driver support for the Altera Cyclone II FPGA + present on the TS-73xx SBC boards. + +config FPGA_BRIDGE + tristate "FPGA Bridge Framework" + 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. + +config XILINX_PR_DECOUPLER + tristate "Xilinx LogiCORE PR Decoupler" + depends on FPGA_BRIDGE + depends on HAS_IOMEM + help + Say Y to enable drivers for Xilinx LogiCORE PR Decoupler. + The PR Decoupler exists in the FPGA fabric to isolate one + region of the FPGA from the busses while that region is + being reprogrammed during partial reconfig. + +config FPGA_REGION + tristate "FPGA Region" + depends on FPGA_BRIDGE + help + FPGA Region common code. A FPGA Region controls a FPGA Manager + and the FPGA Bridges associated with either a reconfigurable + region of an FPGA or a whole FPGA. + +config OF_FPGA_REGION + tristate "FPGA Region Device Tree Overlay Support" + depends on OF && FPGA_REGION + help + Support for loading FPGA images by applying a Device Tree + overlay. + +config FPGA_DFL + tristate "FPGA Device Feature List (DFL) support" + select FPGA_BRIDGE + select FPGA_REGION + help + Device Feature List (DFL) defines a feature list structure that + creates a linked list of feature headers within the MMIO space + to provide an extensible way of adding features for FPGA. + Driver can walk through the feature headers to enumerate feature + devices (e.g. FPGA Management Engine, Port and Accelerator + Function Unit) and their private features for target FPGA devices. + + Select this option to enable common support for Field-Programmable + Gate Array (FPGA) solutions which implement Device Feature List. + It provides enumeration APIs and feature device infrastructure. + +config FPGA_DFL_FME + tristate "FPGA DFL FME Driver" + depends on FPGA_DFL + help + The FPGA Management Engine (FME) is a feature device implemented + under Device Feature List (DFL) framework. Select this option to + enable the platform device driver for FME which implements all + FPGA platform level management features. There shall be one FME + per DFL based FPGA device. + +config FPGA_DFL_FME_MGR + tristate "FPGA DFL FME Manager Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Manager driver for FPGA Management Engine. + +config FPGA_DFL_FME_BRIDGE + tristate "FPGA DFL FME Bridge Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Bridge driver for FPGA Management Engine. + +config FPGA_DFL_FME_REGION + tristate "FPGA DFL FME Region Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Region driver for FPGA Management Engine. + +config FPGA_DFL_AFU + tristate "FPGA DFL AFU Driver" + depends on FPGA_DFL + help + This is the driver for FPGA Accelerated Function Unit (AFU) which + implements AFU and Port management features. A User AFU connects + to the FPGA infrastructure via a Port. There may be more than one + Port/AFU per DFL based FPGA device. + +config FPGA_DFL_PCI + tristate "FPGA DFL PCIe Device Driver" + depends on PCI && FPGA_DFL + help + Select this option to enable PCIe driver for PCIe-based + Field-Programmable Gate Array (FPGA) solutions which implement + the Device Feature List (DFL). This driver provides interfaces + for userspace applications to configure, enumerate, open and access + FPGA accelerators on the FPGA DFL devices, enables system level + management functions such as FPGA partial reconfiguration, power + management and virtualization with DFL framework and DFL feature + device drivers. + + To compile this as a module, choose M here. + +endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile new file mode 100644 index 000000000..7a2d73ba7 --- /dev/null +++ b/drivers/fpga/Makefile @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the fpga framework and fpga manager drivers. +# + +# Core FPGA Manager Framework +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_MACHXO2_SPI) += machxo2-spi.o +obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o +obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o +obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o +obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o +obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o +obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o +obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.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 +obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o + +# High Level Interfaces +obj-$(CONFIG_FPGA_REGION) += fpga-region.o +obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o + +# FPGA Device Feature List Support +obj-$(CONFIG_FPGA_DFL) += dfl.o +obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o +obj-$(CONFIG_FPGA_DFL_FME_MGR) += dfl-fme-mgr.o +obj-$(CONFIG_FPGA_DFL_FME_BRIDGE) += dfl-fme-br.o +obj-$(CONFIG_FPGA_DFL_FME_REGION) += dfl-fme-region.o +obj-$(CONFIG_FPGA_DFL_AFU) += dfl-afu.o + +dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o +dfl-afu-objs := dfl-afu-main.o dfl-afu-region.o dfl-afu-dma-region.o + +# Drivers for FPGAs which implement DFL +obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/altera-cvp.c b/drivers/fpga/altera-cvp.c new file mode 100644 index 000000000..7a42c194b --- /dev/null +++ b/drivers/fpga/altera-cvp.c @@ -0,0 +1,534 @@ +/* + * 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 chkcfg_show(struct device_driver *dev, char *buf) +{ + return snprintf(buf, 3, "%d\n", altera_cvp_chkcfg); +} + +static ssize_t chkcfg_store(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_RW(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); + +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; + struct fpga_manager *mgr; + u16 cmd, val; + u32 regval; + 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; + } + + pci_read_config_dword(pdev, VSE_CVP_STATUS, ®val); + if (!(regval & VSE_CVP_STATUS_CVP_EN)) { + dev_err(&pdev->dev, + "CVP is disabled for this device: CVP_STATUS Reg 0x%x\n", + regval); + 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)); + + mgr = fpga_mgr_create(&pdev->dev, conf->mgr_name, + &altera_cvp_ops, conf); + if (!mgr) { + ret = -ENOMEM; + goto err_unmap; + } + + pci_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) { + fpga_mgr_free(mgr); + goto err_unmap; + } + + return 0; + +err_unmap: + if (conf->map) + 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; + + fpga_mgr_unregister(mgr); + if (conf->map) + 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); +} + +static int __init altera_cvp_init(void) +{ + int ret; + + ret = pci_register_driver(&altera_cvp_driver); + if (ret) + return ret; + + ret = driver_create_file(&altera_cvp_driver.driver, + &driver_attr_chkcfg); + if (ret) + pr_warn("Can't create sysfs chkcfg file\n"); + + return 0; +} + +static void __exit altera_cvp_exit(void) +{ + driver_remove_file(&altera_cvp_driver.driver, &driver_attr_chkcfg); + pci_unregister_driver(&altera_cvp_driver); +} + +module_init(altera_cvp_init); +module_exit(altera_cvp_exit); + +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-fpga2sdram.c b/drivers/fpga/altera-fpga2sdram.c new file mode 100644 index 000000000..23660ccd6 --- /dev/null +++ b/drivers/fpga/altera-fpga2sdram.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA to SDRAM Bridge Driver for Altera SoCFPGA Devices + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + */ + +/* + * 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; + struct fpga_bridge *br; + 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); + + br = fpga_bridge_create(dev, F2S_BRIDGE_NAME, + &altera_fpga2sdram_br_ops, priv); + if (!br) + return -ENOMEM; + + platform_set_drvdata(pdev, br); + + ret = fpga_bridge_register(br); + if (ret) { + fpga_bridge_free(br); + 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(br); + return ret; + } + } + } + + return ret; +} + +static int alt_fpga_bridge_remove(struct platform_device *pdev) +{ + struct fpga_bridge *br = platform_get_drvdata(pdev); + + fpga_bridge_unregister(br); + + 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 000000000..ffd586c48 --- /dev/null +++ b/drivers/fpga/altera-freeze-bridge.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Freeze Bridge Controller + * + * Copyright (C) 2016 Altera Corporation. All rights reserved. + */ +#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_OFFICIAL_VERSION 0xad000003 + +#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 const 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; + void __iomem *base_addr; + struct altera_freeze_br_data *priv; + struct fpga_bridge *br; + struct resource *res; + u32 status, revision; + int ret; + + if (!np) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(base_addr)) + return PTR_ERR(base_addr); + + revision = readl(base_addr + FREEZE_CSR_REG_VERSION); + if ((revision != FREEZE_CSR_SUPPORTED_VERSION) && + (revision != FREEZE_CSR_OFFICIAL_VERSION)) { + dev_err(dev, + "%s unexpected revision 0x%x != 0x%x != 0x%x\n", + __func__, revision, FREEZE_CSR_SUPPORTED_VERSION, + FREEZE_CSR_OFFICIAL_VERSION); + return -EINVAL; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + status = readl(base_addr + FREEZE_CSR_STATUS_OFFSET); + if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE) + priv->enable = 1; + + priv->base_addr = base_addr; + + br = fpga_bridge_create(dev, FREEZE_BRIDGE_NAME, + &altera_freeze_br_br_ops, priv); + if (!br) + return -ENOMEM; + + platform_set_drvdata(pdev, br); + + ret = fpga_bridge_register(br); + if (ret) { + fpga_bridge_free(br); + return ret; + } + + return 0; +} + +static int altera_freeze_br_remove(struct platform_device *pdev) +{ + struct fpga_bridge *br = platform_get_drvdata(pdev); + + fpga_bridge_unregister(br); + + 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 000000000..a974d3f60 --- /dev/null +++ b/drivers/fpga/altera-hps2fpga.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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 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 DEFINE_SPINLOCK(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; + struct fpga_bridge *br; + u32 enable; + 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_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); + } + + 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; + } + + 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) + goto err; + } + } + + br = fpga_bridge_create(dev, priv->name, &altera_hps2fpga_br_ops, priv); + if (!br) { + ret = -ENOMEM; + goto err; + } + + platform_set_drvdata(pdev, br); + + ret = fpga_bridge_register(br); + if (ret) + goto err_free; + + return 0; + +err_free: + fpga_bridge_free(br); +err: + clk_disable_unprepare(priv->clk); + + return ret; +} + +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(bridge); + + 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/altera-pr-ip-core-plat.c b/drivers/fpga/altera-pr-ip-core-plat.c new file mode 100644 index 000000000..b293d8314 --- /dev/null +++ b/drivers/fpga/altera-pr-ip-core-plat.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016-2017 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull <atull@opensource.altera.com> + */ +#include <linux/fpga/altera-pr-ip-core.h> +#include <linux/module.h> +#include <linux/of_device.h> + +static int alt_pr_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + void __iomem *reg_base; + struct resource *res; + + /* 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); + + return alt_pr_register(dev, reg_base); +} + +static int alt_pr_platform_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + return alt_pr_unregister(dev); +} + +static const struct of_device_id alt_pr_of_match[] = { + { .compatible = "altr,a10-pr-ip", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, alt_pr_of_match); + +static struct platform_driver alt_pr_platform_driver = { + .probe = alt_pr_platform_probe, + .remove = alt_pr_platform_remove, + .driver = { + .name = "alt_a10_pr_ip", + .of_match_table = alt_pr_of_match, + }, +}; + +module_platform_driver(alt_pr_platform_driver); +MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>"); +MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Platform Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-pr-ip-core.c b/drivers/fpga/altera-pr-ip-core.c new file mode 100644 index 000000000..65e0b6a2c --- /dev/null +++ b/drivers/fpga/altera-pr-ip-core.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Altera Partial Reconfiguration IP Core + * + * Copyright (C) 2016-2017 Intel Corporation + * + * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation + * by Alan Tull <atull@opensource.altera.com> + */ +#include <linux/delay.h> +#include <linux/fpga/altera-pr-ip-core.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/module.h> + +#define ALT_PR_DATA_OFST 0x00 +#define ALT_PR_CSR_OFST 0x04 + +#define ALT_PR_CSR_PR_START BIT(0) +#define ALT_PR_CSR_STATUS_SFT 2 +#define ALT_PR_CSR_STATUS_MSK (7 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_NRESET (0 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_ERR (1 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_CRC_ERR (2 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_BAD_BITS (3 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_IN_PROG (4 << ALT_PR_CSR_STATUS_SFT) +#define ALT_PR_CSR_STATUS_PR_SUCCESS (5 << ALT_PR_CSR_STATUS_SFT) + +struct alt_pr_priv { + void __iomem *reg_base; +}; + +static enum fpga_mgr_states alt_pr_fpga_state(struct fpga_manager *mgr) +{ + struct alt_pr_priv *priv = mgr->priv; + const char *err = "unknown"; + enum fpga_mgr_states ret = FPGA_MGR_STATE_UNKNOWN; + u32 val; + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + val &= ALT_PR_CSR_STATUS_MSK; + + switch (val) { + case ALT_PR_CSR_STATUS_NRESET: + return FPGA_MGR_STATE_RESET; + + case ALT_PR_CSR_STATUS_PR_ERR: + err = "pr error"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_CRC_ERR: + err = "crc error"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_BAD_BITS: + err = "bad bits"; + ret = FPGA_MGR_STATE_WRITE_ERR; + break; + + case ALT_PR_CSR_STATUS_PR_IN_PROG: + return FPGA_MGR_STATE_WRITE; + + case ALT_PR_CSR_STATUS_PR_SUCCESS: + return FPGA_MGR_STATE_OPERATING; + + default: + break; + } + + dev_err(&mgr->dev, "encountered error code %d (%s) in %s()\n", + val, err, __func__); + return ret; +} + +static int alt_pr_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct alt_pr_priv *priv = mgr->priv; + u32 val; + + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&mgr->dev, "%s Partial Reconfiguration flag not set\n", + __func__); + return -EINVAL; + } + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + if (val & ALT_PR_CSR_PR_START) { + dev_err(&mgr->dev, + "%s Partial Reconfiguration already started\n", + __func__); + return -EINVAL; + } + + writel(val | ALT_PR_CSR_PR_START, priv->reg_base + ALT_PR_CSR_OFST); + + return 0; +} + +static int alt_pr_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct alt_pr_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->reg_base); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks */ + switch (count) { + case 3: + writel(buffer_32[i++] & 0x00ffffff, priv->reg_base); + break; + case 2: + writel(buffer_32[i++] & 0x0000ffff, priv->reg_base); + break; + case 1: + writel(buffer_32[i++] & 0x000000ff, priv->reg_base); + break; + case 0: + break; + default: + /* This will never happen */ + return -EFAULT; + } + + if (alt_pr_fpga_state(mgr) == FPGA_MGR_STATE_WRITE_ERR) + return -EIO; + + return 0; +} + +static int alt_pr_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + u32 i = 0; + + do { + switch (alt_pr_fpga_state(mgr)) { + case FPGA_MGR_STATE_WRITE_ERR: + return -EIO; + + case FPGA_MGR_STATE_OPERATING: + dev_info(&mgr->dev, + "successful partial reconfiguration\n"); + return 0; + + default: + break; + } + udelay(1); + } while (info->config_complete_timeout_us > i++); + + dev_err(&mgr->dev, "timed out waiting for write to complete\n"); + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops alt_pr_ops = { + .state = alt_pr_fpga_state, + .write_init = alt_pr_fpga_write_init, + .write = alt_pr_fpga_write, + .write_complete = alt_pr_fpga_write_complete, +}; + +int alt_pr_register(struct device *dev, void __iomem *reg_base) +{ + struct alt_pr_priv *priv; + struct fpga_manager *mgr; + int ret; + u32 val; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->reg_base = reg_base; + + val = readl(priv->reg_base + ALT_PR_CSR_OFST); + + dev_dbg(dev, "%s status=%d start=%d\n", __func__, + (val & ALT_PR_CSR_STATUS_MSK) >> ALT_PR_CSR_STATUS_SFT, + (int)(val & ALT_PR_CSR_PR_START)); + + mgr = fpga_mgr_create(dev, dev_name(dev), &alt_pr_ops, priv); + if (!mgr) + return -ENOMEM; + + dev_set_drvdata(dev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} +EXPORT_SYMBOL_GPL(alt_pr_register); + +int alt_pr_unregister(struct device *dev) +{ + struct fpga_manager *mgr = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + fpga_mgr_unregister(mgr); + + return 0; +} +EXPORT_SYMBOL_GPL(alt_pr_unregister); + +MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>"); +MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/altera-ps-spi.c b/drivers/fpga/altera-ps-spi.c new file mode 100644 index 000000000..4925cae7d --- /dev/null +++ b/drivers/fpga/altera-ps-spi.c @@ -0,0 +1,325 @@ +/* + * 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 (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; + struct fpga_manager *mgr; + int ret; + + 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_LOW); + 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_optional(&spi->dev, "confd", GPIOD_IN); + if (IS_ERR(conf->confd)) { + dev_err(&spi->dev, "Failed to get confd gpio: %ld\n", + PTR_ERR(conf->confd)); + return PTR_ERR(conf->confd); + } else if (!conf->confd) { + dev_warn(&spi->dev, "Not using confd gpio"); + } + + /* Register manager with unique name */ + snprintf(conf->mgr_name, sizeof(conf->mgr_name), "%s %s", + dev_driver_string(&spi->dev), dev_name(&spi->dev)); + + mgr = fpga_mgr_create(&spi->dev, conf->mgr_name, + &altera_ps_ops, conf); + if (!mgr) + return -ENOMEM; + + spi_set_drvdata(spi, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int altera_ps_remove(struct spi_device *spi) +{ + struct fpga_manager *mgr = spi_get_drvdata(spi); + + fpga_mgr_unregister(mgr); + + 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/dfl-afu-dma-region.c b/drivers/fpga/dfl-afu-dma-region.c new file mode 100644 index 000000000..e056965ef --- /dev/null +++ b/drivers/fpga/dfl-afu-dma-region.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) DMA Region Management + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ + +#include <linux/dma-mapping.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> + +#include "dfl-afu.h" + +static void put_all_pages(struct page **pages, int npages) +{ + int i; + + for (i = 0; i < npages; i++) + if (pages[i]) + put_page(pages[i]); +} + +void afu_dma_region_init(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + + afu->dma_regions = RB_ROOT; +} + +/** + * afu_dma_adjust_locked_vm - adjust locked memory + * @dev: port device + * @npages: number of pages + * @incr: increase or decrease locked memory + * + * Increase or decrease the locked memory size with npages input. + * + * Return 0 on success. + * Return -ENOMEM if locked memory size is over the limit and no CAP_IPC_LOCK. + */ +static int afu_dma_adjust_locked_vm(struct device *dev, long npages, bool incr) +{ + unsigned long locked, lock_limit; + int ret = 0; + + /* the task is exiting. */ + if (!current->mm) + return 0; + + down_write(¤t->mm->mmap_sem); + + if (incr) { + locked = current->mm->locked_vm + npages; + lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + + if (locked > lock_limit && !capable(CAP_IPC_LOCK)) + ret = -ENOMEM; + else + current->mm->locked_vm += npages; + } else { + if (WARN_ON_ONCE(npages > current->mm->locked_vm)) + npages = current->mm->locked_vm; + current->mm->locked_vm -= npages; + } + + dev_dbg(dev, "[%d] RLIMIT_MEMLOCK %c%ld %ld/%ld%s\n", current->pid, + incr ? '+' : '-', npages << PAGE_SHIFT, + current->mm->locked_vm << PAGE_SHIFT, rlimit(RLIMIT_MEMLOCK), + ret ? "- execeeded" : ""); + + up_write(¤t->mm->mmap_sem); + + return ret; +} + +/** + * afu_dma_pin_pages - pin pages of given dma memory region + * @pdata: feature device platform data + * @region: dma memory region to be pinned + * + * Pin all the pages of given dfl_afu_dma_region. + * Return 0 for success or negative error code. + */ +static int afu_dma_pin_pages(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + int npages = region->length >> PAGE_SHIFT; + struct device *dev = &pdata->dev->dev; + int ret, pinned; + + ret = afu_dma_adjust_locked_vm(dev, npages, true); + if (ret) + return ret; + + region->pages = kcalloc(npages, sizeof(struct page *), GFP_KERNEL); + if (!region->pages) { + ret = -ENOMEM; + goto unlock_vm; + } + + pinned = get_user_pages_fast(region->user_addr, npages, 1, + region->pages); + if (pinned < 0) { + ret = pinned; + goto free_pages; + } else if (pinned != npages) { + ret = -EFAULT; + goto put_pages; + } + + dev_dbg(dev, "%d pages pinned\n", pinned); + + return 0; + +put_pages: + put_all_pages(region->pages, pinned); +free_pages: + kfree(region->pages); +unlock_vm: + afu_dma_adjust_locked_vm(dev, npages, false); + return ret; +} + +/** + * afu_dma_unpin_pages - unpin pages of given dma memory region + * @pdata: feature device platform data + * @region: dma memory region to be unpinned + * + * Unpin all the pages of given dfl_afu_dma_region. + * Return 0 for success or negative error code. + */ +static void afu_dma_unpin_pages(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + long npages = region->length >> PAGE_SHIFT; + struct device *dev = &pdata->dev->dev; + + put_all_pages(region->pages, npages); + kfree(region->pages); + afu_dma_adjust_locked_vm(dev, npages, false); + + dev_dbg(dev, "%ld pages unpinned\n", npages); +} + +/** + * afu_dma_check_continuous_pages - check if pages are continuous + * @region: dma memory region + * + * Return true if pages of given dma memory region have continuous physical + * address, otherwise return false. + */ +static bool afu_dma_check_continuous_pages(struct dfl_afu_dma_region *region) +{ + int npages = region->length >> PAGE_SHIFT; + int i; + + for (i = 0; i < npages - 1; i++) + if (page_to_pfn(region->pages[i]) + 1 != + page_to_pfn(region->pages[i + 1])) + return false; + + return true; +} + +/** + * dma_region_check_iova - check if memory area is fully contained in the region + * @region: dma memory region + * @iova: address of the dma memory area + * @size: size of the dma memory area + * + * Compare the dma memory area defined by @iova and @size with given dma region. + * Return true if memory area is fully contained in the region, otherwise false. + */ +static bool dma_region_check_iova(struct dfl_afu_dma_region *region, + u64 iova, u64 size) +{ + if (!size && region->iova != iova) + return false; + + return (region->iova <= iova) && + (region->length + region->iova >= iova + size); +} + +/** + * afu_dma_region_add - add given dma region to rbtree + * @pdata: feature device platform data + * @region: dma region to be added + * + * Return 0 for success, -EEXIST if dma region has already been added. + * + * Needs to be called with pdata->lock heold. + */ +static int afu_dma_region_add(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node **new, *parent = NULL; + + dev_dbg(&pdata->dev->dev, "add region (iova = %llx)\n", + (unsigned long long)region->iova); + + new = &afu->dma_regions.rb_node; + + while (*new) { + struct dfl_afu_dma_region *this; + + this = container_of(*new, struct dfl_afu_dma_region, node); + + parent = *new; + + if (dma_region_check_iova(this, region->iova, region->length)) + return -EEXIST; + + if (region->iova < this->iova) + new = &((*new)->rb_left); + else if (region->iova > this->iova) + new = &((*new)->rb_right); + else + return -EEXIST; + } + + rb_link_node(®ion->node, parent, new); + rb_insert_color(®ion->node, &afu->dma_regions); + + return 0; +} + +/** + * afu_dma_region_remove - remove given dma region from rbtree + * @pdata: feature device platform data + * @region: dma region to be removed + * + * Needs to be called with pdata->lock heold. + */ +static void afu_dma_region_remove(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + struct dfl_afu *afu; + + dev_dbg(&pdata->dev->dev, "del region (iova = %llx)\n", + (unsigned long long)region->iova); + + afu = dfl_fpga_pdata_get_private(pdata); + rb_erase(®ion->node, &afu->dma_regions); +} + +/** + * afu_dma_region_destroy - destroy all regions in rbtree + * @pdata: feature device platform data + * + * Needs to be called with pdata->lock heold. + */ +void afu_dma_region_destroy(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node *node = rb_first(&afu->dma_regions); + struct dfl_afu_dma_region *region; + + while (node) { + region = container_of(node, struct dfl_afu_dma_region, node); + + dev_dbg(&pdata->dev->dev, "del region (iova = %llx)\n", + (unsigned long long)region->iova); + + rb_erase(node, &afu->dma_regions); + + if (region->iova) + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, + DMA_BIDIRECTIONAL); + + if (region->pages) + afu_dma_unpin_pages(pdata, region); + + node = rb_next(node); + kfree(region); + } +} + +/** + * afu_dma_region_find - find the dma region from rbtree based on iova and size + * @pdata: feature device platform data + * @iova: address of the dma memory area + * @size: size of the dma memory area + * + * It finds the dma region from the rbtree based on @iova and @size: + * - if @size == 0, it finds the dma region which starts from @iova + * - otherwise, it finds the dma region which fully contains + * [@iova, @iova+size) + * If nothing is matched returns NULL. + * + * Needs to be called with pdata->lock held. + */ +struct dfl_afu_dma_region * +afu_dma_region_find(struct dfl_feature_platform_data *pdata, u64 iova, u64 size) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node *node = afu->dma_regions.rb_node; + struct device *dev = &pdata->dev->dev; + + while (node) { + struct dfl_afu_dma_region *region; + + region = container_of(node, struct dfl_afu_dma_region, node); + + if (dma_region_check_iova(region, iova, size)) { + dev_dbg(dev, "find region (iova = %llx)\n", + (unsigned long long)region->iova); + return region; + } + + if (iova < region->iova) + node = node->rb_left; + else if (iova > region->iova) + node = node->rb_right; + else + /* the iova region is not fully covered. */ + break; + } + + dev_dbg(dev, "region with iova %llx and size %llx is not found\n", + (unsigned long long)iova, (unsigned long long)size); + + return NULL; +} + +/** + * afu_dma_region_find_iova - find the dma region from rbtree by iova + * @pdata: feature device platform data + * @iova: address of the dma region + * + * Needs to be called with pdata->lock held. + */ +static struct dfl_afu_dma_region * +afu_dma_region_find_iova(struct dfl_feature_platform_data *pdata, u64 iova) +{ + return afu_dma_region_find(pdata, iova, 0); +} + +/** + * afu_dma_map_region - map memory region for dma + * @pdata: feature device platform data + * @user_addr: address of the memory region + * @length: size of the memory region + * @iova: pointer of iova address + * + * Map memory region defined by @user_addr and @length, and return dma address + * of the memory region via @iova. + * Return 0 for success, otherwise error code. + */ +int afu_dma_map_region(struct dfl_feature_platform_data *pdata, + u64 user_addr, u64 length, u64 *iova) +{ + struct dfl_afu_dma_region *region; + int ret; + + /* + * Check Inputs, only accept page-aligned user memory region with + * valid length. + */ + if (!PAGE_ALIGNED(user_addr) || !PAGE_ALIGNED(length) || !length) + return -EINVAL; + + /* Check overflow */ + if (user_addr + length < user_addr) + return -EINVAL; + + if (!access_ok(VERIFY_WRITE, (void __user *)(unsigned long)user_addr, + length)) + return -EINVAL; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->user_addr = user_addr; + region->length = length; + + /* Pin the user memory region */ + ret = afu_dma_pin_pages(pdata, region); + if (ret) { + dev_err(&pdata->dev->dev, "failed to pin memory region\n"); + goto free_region; + } + + /* Only accept continuous pages, return error else */ + if (!afu_dma_check_continuous_pages(region)) { + dev_err(&pdata->dev->dev, "pages are not continuous\n"); + ret = -EINVAL; + goto unpin_pages; + } + + /* As pages are continuous then start to do DMA mapping */ + region->iova = dma_map_page(dfl_fpga_pdata_to_parent(pdata), + region->pages[0], 0, + region->length, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(dfl_fpga_pdata_to_parent(pdata), region->iova)) { + dev_err(&pdata->dev->dev, "failed to map for dma\n"); + ret = -EFAULT; + goto unpin_pages; + } + + *iova = region->iova; + + mutex_lock(&pdata->lock); + ret = afu_dma_region_add(pdata, region); + mutex_unlock(&pdata->lock); + if (ret) { + dev_err(&pdata->dev->dev, "failed to add dma region\n"); + goto unmap_dma; + } + + return 0; + +unmap_dma: + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, DMA_BIDIRECTIONAL); +unpin_pages: + afu_dma_unpin_pages(pdata, region); +free_region: + kfree(region); + return ret; +} + +/** + * afu_dma_unmap_region - unmap dma memory region + * @pdata: feature device platform data + * @iova: dma address of the region + * + * Unmap dma memory region based on @iova. + * Return 0 for success, otherwise error code. + */ +int afu_dma_unmap_region(struct dfl_feature_platform_data *pdata, u64 iova) +{ + struct dfl_afu_dma_region *region; + + mutex_lock(&pdata->lock); + region = afu_dma_region_find_iova(pdata, iova); + if (!region) { + mutex_unlock(&pdata->lock); + return -EINVAL; + } + + if (region->in_use) { + mutex_unlock(&pdata->lock); + return -EBUSY; + } + + afu_dma_region_remove(pdata, region); + mutex_unlock(&pdata->lock); + + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, DMA_BIDIRECTIONAL); + afu_dma_unpin_pages(pdata, region); + kfree(region); + + return 0; +} diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c new file mode 100644 index 000000000..fc048f9a9 --- /dev/null +++ b/drivers/fpga/dfl-afu-main.c @@ -0,0 +1,637 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/fpga-dfl.h> + +#include "dfl-afu.h" + +/** + * port_enable - enable a port + * @pdev: port platform device. + * + * Enable Port by clear the port soft reset bit, which is set by default. + * The AFU is unable to respond to any MMIO access while in reset. + * port_enable function should only be used after port_disable function. + */ +static void port_enable(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + u64 v; + + WARN_ON(!pdata->disable_count); + + if (--pdata->disable_count != 0) + return; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + /* Clear port soft reset */ + v = readq(base + PORT_HDR_CTRL); + v &= ~PORT_CTRL_SFTRST; + writeq(v, base + PORT_HDR_CTRL); +} + +#define RST_POLL_INVL 10 /* us */ +#define RST_POLL_TIMEOUT 1000 /* us */ + +/** + * port_disable - disable a port + * @pdev: port platform device. + * + * Disable Port by setting the port soft reset bit, it puts the port into + * reset. + */ +static int port_disable(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + u64 v; + + if (pdata->disable_count++ != 0) + return 0; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + /* Set port soft reset */ + v = readq(base + PORT_HDR_CTRL); + v |= PORT_CTRL_SFTRST; + writeq(v, base + PORT_HDR_CTRL); + + /* + * HW sets ack bit to 1 when all outstanding requests have been drained + * on this port and minimum soft reset pulse width has elapsed. + * Driver polls port_soft_reset_ack to determine if reset done by HW. + */ + if (readq_poll_timeout(base + PORT_HDR_CTRL, v, + v & PORT_CTRL_SFTRST_ACK, + RST_POLL_INVL, RST_POLL_TIMEOUT)) { + dev_err(&pdev->dev, "timeout, fail to reset device\n"); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * This function resets the FPGA Port and its accelerator (AFU) by function + * __port_disable and __port_enable (set port soft reset bit and then clear + * it). Userspace can do Port reset at any time, e.g. during DMA or Partial + * Reconfiguration. But it should never cause any system level issue, only + * functional failure (e.g. DMA or PR operation failure) and be recoverable + * from the failure. + * + * Note: the accelerator (AFU) is not accessible when its port is in reset + * (disabled). Any attempts on MMIO access to AFU while in reset, will + * result errors reported via port error reporting sub feature (if present). + */ +static int __port_reset(struct platform_device *pdev) +{ + int ret; + + ret = port_disable(pdev); + if (!ret) + port_enable(pdev); + + return ret; +} + +static int port_reset(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret; + + mutex_lock(&pdata->lock); + ret = __port_reset(pdev); + mutex_unlock(&pdata->lock); + + return ret; +} + +static int port_get_id(struct platform_device *pdev) +{ + void __iomem *base; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + return FIELD_GET(PORT_CAP_PORT_NUM, readq(base + PORT_HDR_CAP)); +} + +static ssize_t +id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int id = port_get_id(to_platform_device(dev)); + + return scnprintf(buf, PAGE_SIZE, "%d\n", id); +} +static DEVICE_ATTR_RO(id); + +static const struct attribute *port_hdr_attrs[] = { + &dev_attr_id.attr, + NULL, +}; + +static int port_hdr_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT HDR Init.\n"); + + port_reset(pdev); + + return sysfs_create_files(&pdev->dev.kobj, port_hdr_attrs); +} + +static void port_hdr_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT HDR UInit.\n"); + + sysfs_remove_files(&pdev->dev.kobj, port_hdr_attrs); +} + +static long +port_hdr_ioctl(struct platform_device *pdev, struct dfl_feature *feature, + unsigned int cmd, unsigned long arg) +{ + long ret; + + switch (cmd) { + case DFL_FPGA_PORT_RESET: + if (!arg) + ret = port_reset(pdev); + else + ret = -EINVAL; + break; + default: + dev_dbg(&pdev->dev, "%x cmd not handled", cmd); + ret = -ENODEV; + } + + return ret; +} + +static const struct dfl_feature_ops port_hdr_ops = { + .init = port_hdr_init, + .uinit = port_hdr_uinit, + .ioctl = port_hdr_ioctl, +}; + +static ssize_t +afu_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); + void __iomem *base; + u64 guidl, guidh; + + base = dfl_get_feature_ioaddr_by_id(dev, PORT_FEATURE_ID_AFU); + + mutex_lock(&pdata->lock); + if (pdata->disable_count) { + mutex_unlock(&pdata->lock); + return -EBUSY; + } + + guidl = readq(base + GUID_L); + guidh = readq(base + GUID_H); + mutex_unlock(&pdata->lock); + + return scnprintf(buf, PAGE_SIZE, "%016llx%016llx\n", guidh, guidl); +} +static DEVICE_ATTR_RO(afu_id); + +static const struct attribute *port_afu_attrs[] = { + &dev_attr_afu_id.attr, + NULL +}; + +static int port_afu_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct resource *res = &pdev->resource[feature->resource_index]; + int ret; + + dev_dbg(&pdev->dev, "PORT AFU Init.\n"); + + ret = afu_mmio_region_add(dev_get_platdata(&pdev->dev), + DFL_PORT_REGION_INDEX_AFU, resource_size(res), + res->start, DFL_PORT_REGION_READ | + DFL_PORT_REGION_WRITE | DFL_PORT_REGION_MMAP); + if (ret) + return ret; + + return sysfs_create_files(&pdev->dev.kobj, port_afu_attrs); +} + +static void port_afu_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT AFU UInit.\n"); + + sysfs_remove_files(&pdev->dev.kobj, port_afu_attrs); +} + +static const struct dfl_feature_ops port_afu_ops = { + .init = port_afu_init, + .uinit = port_afu_uinit, +}; + +static struct dfl_feature_driver port_feature_drvs[] = { + { + .id = PORT_FEATURE_ID_HEADER, + .ops = &port_hdr_ops, + }, + { + .id = PORT_FEATURE_ID_AFU, + .ops = &port_afu_ops, + }, + { + .ops = NULL, + } +}; + +static int afu_open(struct inode *inode, struct file *filp) +{ + struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); + struct dfl_feature_platform_data *pdata; + int ret; + + pdata = dev_get_platdata(&fdev->dev); + if (WARN_ON(!pdata)) + return -ENODEV; + + ret = dfl_feature_dev_use_begin(pdata); + if (ret) + return ret; + + dev_dbg(&fdev->dev, "Device File Open\n"); + filp->private_data = fdev; + + return 0; +} + +static int afu_release(struct inode *inode, struct file *filp) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + + dev_dbg(&pdev->dev, "Device File Release\n"); + + pdata = dev_get_platdata(&pdev->dev); + + mutex_lock(&pdata->lock); + __port_reset(pdev); + afu_dma_region_destroy(pdata); + mutex_unlock(&pdata->lock); + + dfl_feature_dev_use_end(pdata); + + return 0; +} + +static long afu_ioctl_check_extension(struct dfl_feature_platform_data *pdata, + unsigned long arg) +{ + /* No extension support for now */ + return 0; +} + +static long +afu_ioctl_get_info(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_info info; + struct dfl_afu *afu; + unsigned long minsz; + + minsz = offsetofend(struct dfl_fpga_port_info, num_umsgs); + + if (copy_from_user(&info, arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz) + return -EINVAL; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + info.flags = 0; + info.num_regions = afu->num_regions; + info.num_umsgs = afu->num_umsgs; + mutex_unlock(&pdata->lock); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static long afu_ioctl_get_region_info(struct dfl_feature_platform_data *pdata, + void __user *arg) +{ + struct dfl_fpga_port_region_info rinfo; + struct dfl_afu_mmio_region region; + unsigned long minsz; + long ret; + + minsz = offsetofend(struct dfl_fpga_port_region_info, offset); + + if (copy_from_user(&rinfo, arg, minsz)) + return -EFAULT; + + if (rinfo.argsz < minsz || rinfo.padding) + return -EINVAL; + + ret = afu_mmio_region_get_by_index(pdata, rinfo.index, ®ion); + if (ret) + return ret; + + rinfo.flags = region.flags; + rinfo.size = region.size; + rinfo.offset = region.offset; + + if (copy_to_user(arg, &rinfo, sizeof(rinfo))) + return -EFAULT; + + return 0; +} + +static long +afu_ioctl_dma_map(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_dma_map map; + unsigned long minsz; + long ret; + + minsz = offsetofend(struct dfl_fpga_port_dma_map, iova); + + if (copy_from_user(&map, arg, minsz)) + return -EFAULT; + + if (map.argsz < minsz || map.flags) + return -EINVAL; + + ret = afu_dma_map_region(pdata, map.user_addr, map.length, &map.iova); + if (ret) + return ret; + + if (copy_to_user(arg, &map, sizeof(map))) { + afu_dma_unmap_region(pdata, map.iova); + return -EFAULT; + } + + dev_dbg(&pdata->dev->dev, "dma map: ua=%llx, len=%llx, iova=%llx\n", + (unsigned long long)map.user_addr, + (unsigned long long)map.length, + (unsigned long long)map.iova); + + return 0; +} + +static long +afu_ioctl_dma_unmap(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_dma_unmap unmap; + unsigned long minsz; + + minsz = offsetofend(struct dfl_fpga_port_dma_unmap, iova); + + if (copy_from_user(&unmap, arg, minsz)) + return -EFAULT; + + if (unmap.argsz < minsz || unmap.flags) + return -EINVAL; + + return afu_dma_unmap_region(pdata, unmap.iova); +} + +static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + struct dfl_feature *f; + long ret; + + dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); + + pdata = dev_get_platdata(&pdev->dev); + + switch (cmd) { + case DFL_FPGA_GET_API_VERSION: + return DFL_FPGA_API_VERSION; + case DFL_FPGA_CHECK_EXTENSION: + return afu_ioctl_check_extension(pdata, arg); + case DFL_FPGA_PORT_GET_INFO: + return afu_ioctl_get_info(pdata, (void __user *)arg); + case DFL_FPGA_PORT_GET_REGION_INFO: + return afu_ioctl_get_region_info(pdata, (void __user *)arg); + case DFL_FPGA_PORT_DMA_MAP: + return afu_ioctl_dma_map(pdata, (void __user *)arg); + case DFL_FPGA_PORT_DMA_UNMAP: + return afu_ioctl_dma_unmap(pdata, (void __user *)arg); + default: + /* + * Let sub-feature's ioctl function to handle the cmd + * Sub-feature's ioctl returns -ENODEV when cmd is not + * handled in this sub feature, and returns 0 and other + * error code if cmd is handled. + */ + dfl_fpga_dev_for_each_feature(pdata, f) + if (f->ops && f->ops->ioctl) { + ret = f->ops->ioctl(pdev, f, cmd, arg); + if (ret != -ENODEV) + return ret; + } + } + + return -EINVAL; +} + +static int afu_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + u64 size = vma->vm_end - vma->vm_start; + struct dfl_afu_mmio_region region; + u64 offset; + int ret; + + if (!(vma->vm_flags & VM_SHARED)) + return -EINVAL; + + pdata = dev_get_platdata(&pdev->dev); + + offset = vma->vm_pgoff << PAGE_SHIFT; + ret = afu_mmio_region_get_by_offset(pdata, offset, size, ®ion); + if (ret) + return ret; + + if (!(region.flags & DFL_PORT_REGION_MMAP)) + return -EINVAL; + + if ((vma->vm_flags & VM_READ) && !(region.flags & DFL_PORT_REGION_READ)) + return -EPERM; + + if ((vma->vm_flags & VM_WRITE) && + !(region.flags & DFL_PORT_REGION_WRITE)) + return -EPERM; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return remap_pfn_range(vma, vma->vm_start, + (region.phys + (offset - region.offset)) >> PAGE_SHIFT, + size, vma->vm_page_prot); +} + +static const struct file_operations afu_fops = { + .owner = THIS_MODULE, + .open = afu_open, + .release = afu_release, + .unlocked_ioctl = afu_ioctl, + .mmap = afu_mmap, +}; + +static int afu_dev_init(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_afu *afu; + + afu = devm_kzalloc(&pdev->dev, sizeof(*afu), GFP_KERNEL); + if (!afu) + return -ENOMEM; + + afu->pdata = pdata; + + mutex_lock(&pdata->lock); + dfl_fpga_pdata_set_private(pdata, afu); + afu_mmio_region_init(pdata); + afu_dma_region_init(pdata); + mutex_unlock(&pdata->lock); + + return 0; +} + +static int afu_dev_destroy(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_afu *afu; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + afu_mmio_region_destroy(pdata); + afu_dma_region_destroy(pdata); + dfl_fpga_pdata_set_private(pdata, NULL); + mutex_unlock(&pdata->lock); + + return 0; +} + +static int port_enable_set(struct platform_device *pdev, bool enable) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret = 0; + + mutex_lock(&pdata->lock); + if (enable) + port_enable(pdev); + else + ret = port_disable(pdev); + mutex_unlock(&pdata->lock); + + return ret; +} + +static struct dfl_fpga_port_ops afu_port_ops = { + .name = DFL_FPGA_FEATURE_DEV_PORT, + .owner = THIS_MODULE, + .get_id = port_get_id, + .enable_set = port_enable_set, +}; + +static int afu_probe(struct platform_device *pdev) +{ + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + ret = afu_dev_init(pdev); + if (ret) + goto exit; + + ret = dfl_fpga_dev_feature_init(pdev, port_feature_drvs); + if (ret) + goto dev_destroy; + + ret = dfl_fpga_dev_ops_register(pdev, &afu_fops, THIS_MODULE); + if (ret) { + dfl_fpga_dev_feature_uinit(pdev); + goto dev_destroy; + } + + return 0; + +dev_destroy: + afu_dev_destroy(pdev); +exit: + return ret; +} + +static int afu_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s\n", __func__); + + dfl_fpga_dev_ops_unregister(pdev); + dfl_fpga_dev_feature_uinit(pdev); + afu_dev_destroy(pdev); + + return 0; +} + +static struct platform_driver afu_driver = { + .driver = { + .name = DFL_FPGA_FEATURE_DEV_PORT, + }, + .probe = afu_probe, + .remove = afu_remove, +}; + +static int __init afu_init(void) +{ + int ret; + + dfl_fpga_port_ops_add(&afu_port_ops); + + ret = platform_driver_register(&afu_driver); + if (ret) + dfl_fpga_port_ops_del(&afu_port_ops); + + return ret; +} + +static void __exit afu_exit(void) +{ + platform_driver_unregister(&afu_driver); + + dfl_fpga_port_ops_del(&afu_port_ops); +} + +module_init(afu_init); +module_exit(afu_exit); + +MODULE_DESCRIPTION("FPGA Accelerated Function Unit driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-port"); diff --git a/drivers/fpga/dfl-afu-region.c b/drivers/fpga/dfl-afu-region.c new file mode 100644 index 000000000..0804b7a0c --- /dev/null +++ b/drivers/fpga/dfl-afu-region.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) MMIO Region Management + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ +#include "dfl-afu.h" + +/** + * afu_mmio_region_init - init function for afu mmio region support + * @pdata: afu platform device's pdata. + */ +void afu_mmio_region_init(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + + INIT_LIST_HEAD(&afu->regions); +} + +#define for_each_region(region, afu) \ + list_for_each_entry((region), &(afu)->regions, node) + +static struct dfl_afu_mmio_region *get_region_by_index(struct dfl_afu *afu, + u32 region_index) +{ + struct dfl_afu_mmio_region *region; + + for_each_region(region, afu) + if (region->index == region_index) + return region; + + return NULL; +} + +/** + * afu_mmio_region_add - add a mmio region to given feature dev. + * + * @region_index: region index. + * @region_size: region size. + * @phys: region's physical address of this region. + * @flags: region flags (access permission). + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_add(struct dfl_feature_platform_data *pdata, + u32 region_index, u64 region_size, u64 phys, u32 flags) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + region = devm_kzalloc(&pdata->dev->dev, sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->index = region_index; + region->size = region_size; + region->phys = phys; + region->flags = flags; + + mutex_lock(&pdata->lock); + + afu = dfl_fpga_pdata_get_private(pdata); + + /* check if @index already exists */ + if (get_region_by_index(afu, region_index)) { + mutex_unlock(&pdata->lock); + ret = -EEXIST; + goto exit; + } + + region_size = PAGE_ALIGN(region_size); + region->offset = afu->region_cur_offset; + list_add(®ion->node, &afu->regions); + + afu->region_cur_offset += region_size; + afu->num_regions++; + mutex_unlock(&pdata->lock); + + return 0; + +exit: + devm_kfree(&pdata->dev->dev, region); + return ret; +} + +/** + * afu_mmio_region_destroy - destroy all mmio regions under given feature dev. + * @pdata: afu platform device's pdata. + */ +void afu_mmio_region_destroy(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct dfl_afu_mmio_region *tmp, *region; + + list_for_each_entry_safe(region, tmp, &afu->regions, node) + devm_kfree(&pdata->dev->dev, region); +} + +/** + * afu_mmio_region_get_by_index - find an afu region by index. + * @pdata: afu platform device's pdata. + * @region_index: region index. + * @pregion: ptr to region for result. + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, + u32 region_index, + struct dfl_afu_mmio_region *pregion) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + region = get_region_by_index(afu, region_index); + if (!region) { + ret = -EINVAL; + goto exit; + } + *pregion = *region; +exit: + mutex_unlock(&pdata->lock); + return ret; +} + +/** + * afu_mmio_region_get_by_offset - find an afu mmio region by offset and size + * + * @pdata: afu platform device's pdata. + * @offset: region offset from start of the device fd. + * @size: region size. + * @pregion: ptr to region for result. + * + * Find the region which fully contains the region described by input + * parameters (offset and size) from the feature dev's region linked list. + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, + u64 offset, u64 size, + struct dfl_afu_mmio_region *pregion) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + for_each_region(region, afu) + if (region->offset <= offset && + region->offset + region->size >= offset + size) { + *pregion = *region; + goto exit; + } + ret = -EINVAL; +exit: + mutex_unlock(&pdata->lock); + return ret; +} diff --git a/drivers/fpga/dfl-afu.h b/drivers/fpga/dfl-afu.h new file mode 100644 index 000000000..0c7630ae3 --- /dev/null +++ b/drivers/fpga/dfl-afu.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Accelerated Function Unit (AFU) Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#ifndef __DFL_AFU_H +#define __DFL_AFU_H + +#include <linux/mm.h> + +#include "dfl.h" + +/** + * struct dfl_afu_mmio_region - afu mmio region data structure + * + * @index: region index. + * @flags: region flags (access permission). + * @size: region size. + * @offset: region offset from start of the device fd. + * @phys: region's physical address. + * @node: node to add to afu feature dev's region list. + */ +struct dfl_afu_mmio_region { + u32 index; + u32 flags; + u64 size; + u64 offset; + u64 phys; + struct list_head node; +}; + +/** + * struct fpga_afu_dma_region - afu DMA region data structure + * + * @user_addr: region userspace virtual address. + * @length: region length. + * @iova: region IO virtual address. + * @pages: ptr to pages of this region. + * @node: rb tree node. + * @in_use: flag to indicate if this region is in_use. + */ +struct dfl_afu_dma_region { + u64 user_addr; + u64 length; + u64 iova; + struct page **pages; + struct rb_node node; + bool in_use; +}; + +/** + * struct dfl_afu - afu device data structure + * + * @region_cur_offset: current region offset from start to the device fd. + * @num_regions: num of mmio regions. + * @regions: the mmio region linked list of this afu feature device. + * @dma_regions: root of dma regions rb tree. + * @num_umsgs: num of umsgs. + * @pdata: afu platform device's pdata. + */ +struct dfl_afu { + u64 region_cur_offset; + int num_regions; + u8 num_umsgs; + struct list_head regions; + struct rb_root dma_regions; + + struct dfl_feature_platform_data *pdata; +}; + +void afu_mmio_region_init(struct dfl_feature_platform_data *pdata); +int afu_mmio_region_add(struct dfl_feature_platform_data *pdata, + u32 region_index, u64 region_size, u64 phys, u32 flags); +void afu_mmio_region_destroy(struct dfl_feature_platform_data *pdata); +int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, + u32 region_index, + struct dfl_afu_mmio_region *pregion); +int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, + u64 offset, u64 size, + struct dfl_afu_mmio_region *pregion); +void afu_dma_region_init(struct dfl_feature_platform_data *pdata); +void afu_dma_region_destroy(struct dfl_feature_platform_data *pdata); +int afu_dma_map_region(struct dfl_feature_platform_data *pdata, + u64 user_addr, u64 length, u64 *iova); +int afu_dma_unmap_region(struct dfl_feature_platform_data *pdata, u64 iova); +struct dfl_afu_dma_region * +afu_dma_region_find(struct dfl_feature_platform_data *pdata, + u64 iova, u64 size); +#endif /* __DFL_AFU_H */ diff --git a/drivers/fpga/dfl-fme-br.c b/drivers/fpga/dfl-fme-br.c new file mode 100644 index 000000000..7cc041def --- /dev/null +++ b/drivers/fpga/dfl-fme-br.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Bridge Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/module.h> +#include <linux/fpga/fpga-bridge.h> + +#include "dfl.h" +#include "dfl-fme-pr.h" + +struct fme_br_priv { + struct dfl_fme_br_pdata *pdata; + struct dfl_fpga_port_ops *port_ops; + struct platform_device *port_pdev; +}; + +static int fme_bridge_enable_set(struct fpga_bridge *bridge, bool enable) +{ + struct fme_br_priv *priv = bridge->priv; + struct platform_device *port_pdev; + struct dfl_fpga_port_ops *ops; + + if (!priv->port_pdev) { + port_pdev = dfl_fpga_cdev_find_port(priv->pdata->cdev, + &priv->pdata->port_id, + dfl_fpga_check_port_id); + if (!port_pdev) + return -ENODEV; + + priv->port_pdev = port_pdev; + } + + if (priv->port_pdev && !priv->port_ops) { + ops = dfl_fpga_port_ops_get(priv->port_pdev); + if (!ops || !ops->enable_set) + return -ENOENT; + + priv->port_ops = ops; + } + + return priv->port_ops->enable_set(priv->port_pdev, enable); +} + +static const struct fpga_bridge_ops fme_bridge_ops = { + .enable_set = fme_bridge_enable_set, +}; + +static int fme_br_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fme_br_priv *priv; + struct fpga_bridge *br; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdata = dev_get_platdata(dev); + + br = fpga_bridge_create(dev, "DFL FPGA FME Bridge", + &fme_bridge_ops, priv); + if (!br) + return -ENOMEM; + + platform_set_drvdata(pdev, br); + + ret = fpga_bridge_register(br); + if (ret) + fpga_bridge_free(br); + + return ret; +} + +static int fme_br_remove(struct platform_device *pdev) +{ + struct fpga_bridge *br = platform_get_drvdata(pdev); + struct fme_br_priv *priv = br->priv; + + fpga_bridge_unregister(br); + + if (priv->port_pdev) + put_device(&priv->port_pdev->dev); + if (priv->port_ops) + dfl_fpga_port_ops_put(priv->port_ops); + + return 0; +} + +static struct platform_driver fme_br_driver = { + .driver = { + .name = DFL_FPGA_FME_BRIDGE, + }, + .probe = fme_br_probe, + .remove = fme_br_remove, +}; + +module_platform_driver(fme_br_driver); + +MODULE_DESCRIPTION("FPGA Bridge for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-bridge"); diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c new file mode 100644 index 000000000..086ad2420 --- /dev/null +++ b/drivers/fpga/dfl-fme-main.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fpga-dfl.h> + +#include "dfl.h" +#include "dfl-fme.h" + +static ssize_t ports_num_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_CAP); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + (unsigned int)FIELD_GET(FME_CAP_NUM_PORTS, v)); +} +static DEVICE_ATTR_RO(ports_num); + +/* + * Bitstream (static FPGA region) identifier number. It contains the + * detailed version and other information of this static FPGA region. + */ +static ssize_t bitstream_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_BITSTREAM_ID); + + return scnprintf(buf, PAGE_SIZE, "0x%llx\n", (unsigned long long)v); +} +static DEVICE_ATTR_RO(bitstream_id); + +/* + * Bitstream (static FPGA region) meta data. It contains the synthesis + * date, seed and other information of this static FPGA region. + */ +static ssize_t bitstream_metadata_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_BITSTREAM_MD); + + return scnprintf(buf, PAGE_SIZE, "0x%llx\n", (unsigned long long)v); +} +static DEVICE_ATTR_RO(bitstream_metadata); + +static const struct attribute *fme_hdr_attrs[] = { + &dev_attr_ports_num.attr, + &dev_attr_bitstream_id.attr, + &dev_attr_bitstream_metadata.attr, + NULL, +}; + +static int fme_hdr_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + void __iomem *base = feature->ioaddr; + int ret; + + dev_dbg(&pdev->dev, "FME HDR Init.\n"); + dev_dbg(&pdev->dev, "FME cap %llx.\n", + (unsigned long long)readq(base + FME_HDR_CAP)); + + ret = sysfs_create_files(&pdev->dev.kobj, fme_hdr_attrs); + if (ret) + return ret; + + return 0; +} + +static void fme_hdr_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "FME HDR UInit.\n"); + sysfs_remove_files(&pdev->dev.kobj, fme_hdr_attrs); +} + +static const struct dfl_feature_ops fme_hdr_ops = { + .init = fme_hdr_init, + .uinit = fme_hdr_uinit, +}; + +static struct dfl_feature_driver fme_feature_drvs[] = { + { + .id = FME_FEATURE_ID_HEADER, + .ops = &fme_hdr_ops, + }, + { + .id = FME_FEATURE_ID_PR_MGMT, + .ops = &pr_mgmt_ops, + }, + { + .ops = NULL, + }, +}; + +static long fme_ioctl_check_extension(struct dfl_feature_platform_data *pdata, + unsigned long arg) +{ + /* No extension support for now */ + return 0; +} + +static int fme_open(struct inode *inode, struct file *filp) +{ + struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); + struct dfl_feature_platform_data *pdata = dev_get_platdata(&fdev->dev); + int ret; + + if (WARN_ON(!pdata)) + return -ENODEV; + + ret = dfl_feature_dev_use_begin(pdata); + if (ret) + return ret; + + dev_dbg(&fdev->dev, "Device File Open\n"); + filp->private_data = pdata; + + return 0; +} + +static int fme_release(struct inode *inode, struct file *filp) +{ + struct dfl_feature_platform_data *pdata = filp->private_data; + struct platform_device *pdev = pdata->dev; + + dev_dbg(&pdev->dev, "Device File Release\n"); + dfl_feature_dev_use_end(pdata); + + return 0; +} + +static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = filp->private_data; + struct platform_device *pdev = pdata->dev; + struct dfl_feature *f; + long ret; + + dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); + + switch (cmd) { + case DFL_FPGA_GET_API_VERSION: + return DFL_FPGA_API_VERSION; + case DFL_FPGA_CHECK_EXTENSION: + return fme_ioctl_check_extension(pdata, arg); + default: + /* + * Let sub-feature's ioctl function to handle the cmd. + * Sub-feature's ioctl returns -ENODEV when cmd is not + * handled in this sub feature, and returns 0 or other + * error code if cmd is handled. + */ + dfl_fpga_dev_for_each_feature(pdata, f) { + if (f->ops && f->ops->ioctl) { + ret = f->ops->ioctl(pdev, f, cmd, arg); + if (ret != -ENODEV) + return ret; + } + } + } + + return -EINVAL; +} + +static int fme_dev_init(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *fme; + + fme = devm_kzalloc(&pdev->dev, sizeof(*fme), GFP_KERNEL); + if (!fme) + return -ENOMEM; + + fme->pdata = pdata; + + mutex_lock(&pdata->lock); + dfl_fpga_pdata_set_private(pdata, fme); + mutex_unlock(&pdata->lock); + + return 0; +} + +static void fme_dev_destroy(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *fme; + + mutex_lock(&pdata->lock); + fme = dfl_fpga_pdata_get_private(pdata); + dfl_fpga_pdata_set_private(pdata, NULL); + mutex_unlock(&pdata->lock); +} + +static const struct file_operations fme_fops = { + .owner = THIS_MODULE, + .open = fme_open, + .release = fme_release, + .unlocked_ioctl = fme_ioctl, +}; + +static int fme_probe(struct platform_device *pdev) +{ + int ret; + + ret = fme_dev_init(pdev); + if (ret) + goto exit; + + ret = dfl_fpga_dev_feature_init(pdev, fme_feature_drvs); + if (ret) + goto dev_destroy; + + ret = dfl_fpga_dev_ops_register(pdev, &fme_fops, THIS_MODULE); + if (ret) + goto feature_uinit; + + return 0; + +feature_uinit: + dfl_fpga_dev_feature_uinit(pdev); +dev_destroy: + fme_dev_destroy(pdev); +exit: + return ret; +} + +static int fme_remove(struct platform_device *pdev) +{ + dfl_fpga_dev_ops_unregister(pdev); + dfl_fpga_dev_feature_uinit(pdev); + fme_dev_destroy(pdev); + + return 0; +} + +static struct platform_driver fme_driver = { + .driver = { + .name = DFL_FPGA_FEATURE_DEV_FME, + }, + .probe = fme_probe, + .remove = fme_remove, +}; + +module_platform_driver(fme_driver); + +MODULE_DESCRIPTION("FPGA Management Engine driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme"); diff --git a/drivers/fpga/dfl-fme-mgr.c b/drivers/fpga/dfl-fme-mgr.c new file mode 100644 index 000000000..b5ef405b6 --- /dev/null +++ b/drivers/fpga/dfl-fme-mgr.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Manager Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Christopher Rauer <christopher.rauer@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/bitfield.h> +#include <linux/module.h> +#include <linux/iopoll.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/fpga/fpga-mgr.h> + +#include "dfl-fme-pr.h" + +/* FME Partial Reconfiguration Sub Feature Register Set */ +#define FME_PR_DFH 0x0 +#define FME_PR_CTRL 0x8 +#define FME_PR_STS 0x10 +#define FME_PR_DATA 0x18 +#define FME_PR_ERR 0x20 +#define FME_PR_INTFC_ID_H 0xA8 +#define FME_PR_INTFC_ID_L 0xB0 + +/* FME PR Control Register Bitfield */ +#define FME_PR_CTRL_PR_RST BIT_ULL(0) /* Reset PR engine */ +#define FME_PR_CTRL_PR_RSTACK BIT_ULL(4) /* Ack for PR engine reset */ +#define FME_PR_CTRL_PR_RGN_ID GENMASK_ULL(9, 7) /* PR Region ID */ +#define FME_PR_CTRL_PR_START BIT_ULL(12) /* Start to request PR service */ +#define FME_PR_CTRL_PR_COMPLETE BIT_ULL(13) /* PR data push completion */ + +/* FME PR Status Register Bitfield */ +/* Number of available entries in HW queue inside the PR engine. */ +#define FME_PR_STS_PR_CREDIT GENMASK_ULL(8, 0) +#define FME_PR_STS_PR_STS BIT_ULL(16) /* PR operation status */ +#define FME_PR_STS_PR_STS_IDLE 0 +#define FME_PR_STS_PR_CTRLR_STS GENMASK_ULL(22, 20) /* Controller status */ +#define FME_PR_STS_PR_HOST_STS GENMASK_ULL(27, 24) /* PR host status */ + +/* FME PR Data Register Bitfield */ +/* PR data from the raw-binary file. */ +#define FME_PR_DATA_PR_DATA_RAW GENMASK_ULL(32, 0) + +/* FME PR Error Register */ +/* PR Operation errors detected. */ +#define FME_PR_ERR_OPERATION_ERR BIT_ULL(0) +/* CRC error detected. */ +#define FME_PR_ERR_CRC_ERR BIT_ULL(1) +/* Incompatible PR bitstream detected. */ +#define FME_PR_ERR_INCOMPATIBLE_BS BIT_ULL(2) +/* PR data push protocol violated. */ +#define FME_PR_ERR_PROTOCOL_ERR BIT_ULL(3) +/* PR data fifo overflow error detected */ +#define FME_PR_ERR_FIFO_OVERFLOW BIT_ULL(4) + +#define PR_WAIT_TIMEOUT 8000000 +#define PR_HOST_STATUS_IDLE 0 + +struct fme_mgr_priv { + void __iomem *ioaddr; + u64 pr_error; +}; + +static u64 pr_error_to_mgr_status(u64 err) +{ + u64 status = 0; + + if (err & FME_PR_ERR_OPERATION_ERR) + status |= FPGA_MGR_STATUS_OPERATION_ERR; + if (err & FME_PR_ERR_CRC_ERR) + status |= FPGA_MGR_STATUS_CRC_ERR; + if (err & FME_PR_ERR_INCOMPATIBLE_BS) + status |= FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR; + if (err & FME_PR_ERR_PROTOCOL_ERR) + status |= FPGA_MGR_STATUS_IP_PROTOCOL_ERR; + if (err & FME_PR_ERR_FIFO_OVERFLOW) + status |= FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR; + + return status; +} + +static u64 fme_mgr_pr_error_handle(void __iomem *fme_pr) +{ + u64 pr_status, pr_error; + + pr_status = readq(fme_pr + FME_PR_STS); + if (!(pr_status & FME_PR_STS_PR_STS)) + return 0; + + pr_error = readq(fme_pr + FME_PR_ERR); + writeq(pr_error, fme_pr + FME_PR_ERR); + + return pr_error; +} + +static int fme_mgr_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl, pr_status; + + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(dev, "only supports partial reconfiguration.\n"); + return -EINVAL; + } + + dev_dbg(dev, "resetting PR before initiated PR\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_RST; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + if (readq_poll_timeout(fme_pr + FME_PR_CTRL, pr_ctrl, + pr_ctrl & FME_PR_CTRL_PR_RSTACK, 1, + PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Reset ACK timeout\n"); + return -ETIMEDOUT; + } + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl &= ~FME_PR_CTRL_PR_RST; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, + "waiting for PR resource in HW to be initialized and ready\n"); + + if (readq_poll_timeout(fme_pr + FME_PR_STS, pr_status, + (pr_status & FME_PR_STS_PR_STS) == + FME_PR_STS_PR_STS_IDLE, 1, PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Status timeout\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + return -ETIMEDOUT; + } + + dev_dbg(dev, "check and clear previous PR error\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + if (priv->pr_error) + dev_dbg(dev, "previous PR error detected %llx\n", + (unsigned long long)priv->pr_error); + + dev_dbg(dev, "set PR port ID\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl &= ~FME_PR_CTRL_PR_RGN_ID; + pr_ctrl |= FIELD_PREP(FME_PR_CTRL_PR_RGN_ID, info->region_id); + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + return 0; +} + +static int fme_mgr_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl, pr_status, pr_data; + int delay = 0, pr_credit, i = 0; + + dev_dbg(dev, "start request\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_START; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, "pushing data from bitstream to HW\n"); + + /* + * driver can push data to PR hardware using PR_DATA register once HW + * has enough pr_credit (> 1), pr_credit reduces one for every 32bit + * pr data write to PR_DATA register. If pr_credit <= 1, driver needs + * to wait for enough pr_credit from hardware by polling. + */ + pr_status = readq(fme_pr + FME_PR_STS); + pr_credit = FIELD_GET(FME_PR_STS_PR_CREDIT, pr_status); + + while (count > 0) { + while (pr_credit <= 1) { + if (delay++ > PR_WAIT_TIMEOUT) { + dev_err(dev, "PR_CREDIT timeout\n"); + return -ETIMEDOUT; + } + udelay(1); + + pr_status = readq(fme_pr + FME_PR_STS); + pr_credit = FIELD_GET(FME_PR_STS_PR_CREDIT, pr_status); + } + + if (count < 4) { + dev_err(dev, "Invaild PR bitstream size\n"); + return -EINVAL; + } + + pr_data = 0; + pr_data |= FIELD_PREP(FME_PR_DATA_PR_DATA_RAW, + *(((u32 *)buf) + i)); + writeq(pr_data, fme_pr + FME_PR_DATA); + count -= 4; + pr_credit--; + i++; + } + + return 0; +} + +static int fme_mgr_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl; + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_COMPLETE; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, "green bitstream push complete\n"); + dev_dbg(dev, "waiting for HW to release PR resource\n"); + + if (readq_poll_timeout(fme_pr + FME_PR_CTRL, pr_ctrl, + !(pr_ctrl & FME_PR_CTRL_PR_START), 1, + PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Completion ACK timeout.\n"); + return -ETIMEDOUT; + } + + dev_dbg(dev, "PR operation complete, checking status\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + if (priv->pr_error) { + dev_dbg(dev, "PR error detected %llx\n", + (unsigned long long)priv->pr_error); + return -EIO; + } + + dev_dbg(dev, "PR done successfully\n"); + + return 0; +} + +static enum fpga_mgr_states fme_mgr_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static u64 fme_mgr_status(struct fpga_manager *mgr) +{ + struct fme_mgr_priv *priv = mgr->priv; + + return pr_error_to_mgr_status(priv->pr_error); +} + +static const struct fpga_manager_ops fme_mgr_ops = { + .write_init = fme_mgr_write_init, + .write = fme_mgr_write, + .write_complete = fme_mgr_write_complete, + .state = fme_mgr_state, + .status = fme_mgr_status, +}; + +static void fme_mgr_get_compat_id(void __iomem *fme_pr, + struct fpga_compat_id *id) +{ + id->id_l = readq(fme_pr + FME_PR_INTFC_ID_L); + id->id_h = readq(fme_pr + FME_PR_INTFC_ID_H); +} + +static int fme_mgr_probe(struct platform_device *pdev) +{ + struct dfl_fme_mgr_pdata *pdata = dev_get_platdata(&pdev->dev); + struct fpga_compat_id *compat_id; + struct device *dev = &pdev->dev; + struct fme_mgr_priv *priv; + struct fpga_manager *mgr; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (pdata->ioaddr) + priv->ioaddr = pdata->ioaddr; + + if (!priv->ioaddr) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->ioaddr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ioaddr)) + return PTR_ERR(priv->ioaddr); + } + + compat_id = devm_kzalloc(dev, sizeof(*compat_id), GFP_KERNEL); + if (!compat_id) + return -ENOMEM; + + fme_mgr_get_compat_id(priv->ioaddr, compat_id); + + mgr = fpga_mgr_create(dev, "DFL FME FPGA Manager", + &fme_mgr_ops, priv); + if (!mgr) + return -ENOMEM; + + mgr->compat_id = compat_id; + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int fme_mgr_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static struct platform_driver fme_mgr_driver = { + .driver = { + .name = DFL_FPGA_FME_MGR, + }, + .probe = fme_mgr_probe, + .remove = fme_mgr_remove, +}; + +module_platform_driver(fme_mgr_driver); + +MODULE_DESCRIPTION("FPGA Manager for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-mgr"); diff --git a/drivers/fpga/dfl-fme-pr.c b/drivers/fpga/dfl-fme-pr.c new file mode 100644 index 000000000..0b840531e --- /dev/null +++ b/drivers/fpga/dfl-fme-pr.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Management Engine (FME) Partial Reconfiguration + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Christopher Rauer <christopher.rauer@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/vmalloc.h> +#include <linux/uaccess.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/fpga/fpga-bridge.h> +#include <linux/fpga/fpga-region.h> +#include <linux/fpga-dfl.h> + +#include "dfl.h" +#include "dfl-fme.h" +#include "dfl-fme-pr.h" + +static struct dfl_fme_region * +dfl_fme_region_find_by_port_id(struct dfl_fme *fme, int port_id) +{ + struct dfl_fme_region *fme_region; + + list_for_each_entry(fme_region, &fme->region_list, node) + if (fme_region->port_id == port_id) + return fme_region; + + return NULL; +} + +static int dfl_fme_region_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +static struct fpga_region *dfl_fme_region_find(struct dfl_fme *fme, int port_id) +{ + struct dfl_fme_region *fme_region; + struct fpga_region *region; + + fme_region = dfl_fme_region_find_by_port_id(fme, port_id); + if (!fme_region) + return NULL; + + region = fpga_region_class_find(NULL, &fme_region->region->dev, + dfl_fme_region_match); + if (!region) + return NULL; + + return region; +} + +static int fme_pr(struct platform_device *pdev, unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __user *argp = (void __user *)arg; + struct dfl_fpga_fme_port_pr port_pr; + struct fpga_image_info *info; + struct fpga_region *region; + void __iomem *fme_hdr; + struct dfl_fme *fme; + unsigned long minsz; + void *buf = NULL; + int ret = 0; + u64 v; + + minsz = offsetofend(struct dfl_fpga_fme_port_pr, buffer_address); + + if (copy_from_user(&port_pr, argp, minsz)) + return -EFAULT; + + if (port_pr.argsz < minsz || port_pr.flags) + return -EINVAL; + + if (!IS_ALIGNED(port_pr.buffer_size, 4)) + return -EINVAL; + + /* get fme header region */ + fme_hdr = dfl_get_feature_ioaddr_by_id(&pdev->dev, + FME_FEATURE_ID_HEADER); + + /* check port id */ + v = readq(fme_hdr + FME_HDR_CAP); + if (port_pr.port_id >= FIELD_GET(FME_CAP_NUM_PORTS, v)) { + dev_dbg(&pdev->dev, "port number more than maximum\n"); + return -EINVAL; + } + + if (!access_ok(VERIFY_READ, + (void __user *)(unsigned long)port_pr.buffer_address, + port_pr.buffer_size)) + return -EFAULT; + + buf = vmalloc(port_pr.buffer_size); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, + (void __user *)(unsigned long)port_pr.buffer_address, + port_pr.buffer_size)) { + ret = -EFAULT; + goto free_exit; + } + + /* prepare fpga_image_info for PR */ + info = fpga_image_info_alloc(&pdev->dev); + if (!info) { + ret = -ENOMEM; + goto free_exit; + } + + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + mutex_lock(&pdata->lock); + fme = dfl_fpga_pdata_get_private(pdata); + /* fme device has been unregistered. */ + if (!fme) { + ret = -EINVAL; + goto unlock_exit; + } + + region = dfl_fme_region_find(fme, port_pr.port_id); + if (!region) { + ret = -EINVAL; + goto unlock_exit; + } + + fpga_image_info_free(region->info); + + info->buf = buf; + info->count = port_pr.buffer_size; + info->region_id = port_pr.port_id; + region->info = info; + + ret = fpga_region_program_fpga(region); + + /* + * it allows userspace to reset the PR region's logic by disabling and + * reenabling the bridge to clear things out between accleration runs. + * so no need to hold the bridges after partial reconfiguration. + */ + if (region->get_bridges) + fpga_bridges_put(®ion->bridge_list); + + put_device(®ion->dev); +unlock_exit: + mutex_unlock(&pdata->lock); +free_exit: + vfree(buf); + if (copy_to_user((void __user *)arg, &port_pr, minsz)) + return -EFAULT; + + return ret; +} + +/** + * dfl_fme_create_mgr - create fpga mgr platform device as child device + * + * @pdata: fme platform_device's pdata + * + * Return: mgr platform device if successful, and error code otherwise. + */ +static struct platform_device * +dfl_fme_create_mgr(struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature) +{ + struct platform_device *mgr, *fme = pdata->dev; + struct dfl_fme_mgr_pdata mgr_pdata; + int ret = -ENOMEM; + + if (!feature->ioaddr) + return ERR_PTR(-ENODEV); + + mgr_pdata.ioaddr = feature->ioaddr; + + /* + * Each FME has only one fpga-mgr, so allocate platform device using + * the same FME platform device id. + */ + mgr = platform_device_alloc(DFL_FPGA_FME_MGR, fme->id); + if (!mgr) + return ERR_PTR(ret); + + mgr->dev.parent = &fme->dev; + + ret = platform_device_add_data(mgr, &mgr_pdata, sizeof(mgr_pdata)); + if (ret) + goto create_mgr_err; + + ret = platform_device_add(mgr); + if (ret) + goto create_mgr_err; + + return mgr; + +create_mgr_err: + platform_device_put(mgr); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_mgr - destroy fpga mgr platform device + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_mgr(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + + platform_device_unregister(priv->mgr); +} + +/** + * dfl_fme_create_bridge - create fme fpga bridge platform device as child + * + * @pdata: fme platform device's pdata + * @port_id: port id for the bridge to be created. + * + * Return: bridge platform device if successful, and error code otherwise. + */ +static struct dfl_fme_bridge * +dfl_fme_create_bridge(struct dfl_feature_platform_data *pdata, int port_id) +{ + struct device *dev = &pdata->dev->dev; + struct dfl_fme_br_pdata br_pdata; + struct dfl_fme_bridge *fme_br; + int ret = -ENOMEM; + + fme_br = devm_kzalloc(dev, sizeof(*fme_br), GFP_KERNEL); + if (!fme_br) + return ERR_PTR(ret); + + br_pdata.cdev = pdata->dfl_cdev; + br_pdata.port_id = port_id; + + fme_br->br = platform_device_alloc(DFL_FPGA_FME_BRIDGE, + PLATFORM_DEVID_AUTO); + if (!fme_br->br) + return ERR_PTR(ret); + + fme_br->br->dev.parent = dev; + + ret = platform_device_add_data(fme_br->br, &br_pdata, sizeof(br_pdata)); + if (ret) + goto create_br_err; + + ret = platform_device_add(fme_br->br); + if (ret) + goto create_br_err; + + return fme_br; + +create_br_err: + platform_device_put(fme_br->br); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_bridge - destroy fpga bridge platform device + * @fme_br: fme bridge to destroy + */ +static void dfl_fme_destroy_bridge(struct dfl_fme_bridge *fme_br) +{ + platform_device_unregister(fme_br->br); +} + +/** + * dfl_fme_destroy_bridge - destroy all fpga bridge platform device + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_bridges(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + struct dfl_fme_bridge *fbridge, *tmp; + + list_for_each_entry_safe(fbridge, tmp, &priv->bridge_list, node) { + list_del(&fbridge->node); + dfl_fme_destroy_bridge(fbridge); + } +} + +/** + * dfl_fme_create_region - create fpga region platform device as child + * + * @pdata: fme platform device's pdata + * @mgr: mgr platform device needed for region + * @br: br platform device needed for region + * @port_id: port id + * + * Return: fme region if successful, and error code otherwise. + */ +static struct dfl_fme_region * +dfl_fme_create_region(struct dfl_feature_platform_data *pdata, + struct platform_device *mgr, + struct platform_device *br, int port_id) +{ + struct dfl_fme_region_pdata region_pdata; + struct device *dev = &pdata->dev->dev; + struct dfl_fme_region *fme_region; + int ret = -ENOMEM; + + fme_region = devm_kzalloc(dev, sizeof(*fme_region), GFP_KERNEL); + if (!fme_region) + return ERR_PTR(ret); + + region_pdata.mgr = mgr; + region_pdata.br = br; + + /* + * Each FPGA device may have more than one port, so allocate platform + * device using the same port platform device id. + */ + fme_region->region = platform_device_alloc(DFL_FPGA_FME_REGION, br->id); + if (!fme_region->region) + return ERR_PTR(ret); + + fme_region->region->dev.parent = dev; + + ret = platform_device_add_data(fme_region->region, ®ion_pdata, + sizeof(region_pdata)); + if (ret) + goto create_region_err; + + ret = platform_device_add(fme_region->region); + if (ret) + goto create_region_err; + + fme_region->port_id = port_id; + + return fme_region; + +create_region_err: + platform_device_put(fme_region->region); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_region - destroy fme region + * @fme_region: fme region to destroy + */ +static void dfl_fme_destroy_region(struct dfl_fme_region *fme_region) +{ + platform_device_unregister(fme_region->region); +} + +/** + * dfl_fme_destroy_regions - destroy all fme regions + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_regions(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + struct dfl_fme_region *fme_region, *tmp; + + list_for_each_entry_safe(fme_region, tmp, &priv->region_list, node) { + list_del(&fme_region->node); + dfl_fme_destroy_region(fme_region); + } +} + +static int pr_mgmt_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme_region *fme_region; + struct dfl_fme_bridge *fme_br; + struct platform_device *mgr; + struct dfl_fme *priv; + void __iomem *fme_hdr; + int ret = -ENODEV, i = 0; + u64 fme_cap, port_offset; + + fme_hdr = dfl_get_feature_ioaddr_by_id(&pdev->dev, + FME_FEATURE_ID_HEADER); + + mutex_lock(&pdata->lock); + priv = dfl_fpga_pdata_get_private(pdata); + + /* Initialize the region and bridge sub device list */ + INIT_LIST_HEAD(&priv->region_list); + INIT_LIST_HEAD(&priv->bridge_list); + + /* Create fpga mgr platform device */ + mgr = dfl_fme_create_mgr(pdata, feature); + if (IS_ERR(mgr)) { + dev_err(&pdev->dev, "fail to create fpga mgr pdev\n"); + goto unlock; + } + + priv->mgr = mgr; + + /* Read capability register to check number of regions and bridges */ + fme_cap = readq(fme_hdr + FME_HDR_CAP); + for (; i < FIELD_GET(FME_CAP_NUM_PORTS, fme_cap); i++) { + port_offset = readq(fme_hdr + FME_HDR_PORT_OFST(i)); + if (!(port_offset & FME_PORT_OFST_IMP)) + continue; + + /* Create bridge for each port */ + fme_br = dfl_fme_create_bridge(pdata, i); + if (IS_ERR(fme_br)) { + ret = PTR_ERR(fme_br); + goto destroy_region; + } + + list_add(&fme_br->node, &priv->bridge_list); + + /* Create region for each port */ + fme_region = dfl_fme_create_region(pdata, mgr, + fme_br->br, i); + if (IS_ERR(fme_region)) { + ret = PTR_ERR(fme_region); + goto destroy_region; + } + + list_add(&fme_region->node, &priv->region_list); + } + mutex_unlock(&pdata->lock); + + return 0; + +destroy_region: + dfl_fme_destroy_regions(pdata); + dfl_fme_destroy_bridges(pdata); + dfl_fme_destroy_mgr(pdata); +unlock: + mutex_unlock(&pdata->lock); + return ret; +} + +static void pr_mgmt_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *priv; + + mutex_lock(&pdata->lock); + priv = dfl_fpga_pdata_get_private(pdata); + + dfl_fme_destroy_regions(pdata); + dfl_fme_destroy_bridges(pdata); + dfl_fme_destroy_mgr(pdata); + mutex_unlock(&pdata->lock); +} + +static long fme_pr_ioctl(struct platform_device *pdev, + struct dfl_feature *feature, + unsigned int cmd, unsigned long arg) +{ + long ret; + + switch (cmd) { + case DFL_FPGA_FME_PORT_PR: + ret = fme_pr(pdev, arg); + break; + default: + ret = -ENODEV; + } + + return ret; +} + +const struct dfl_feature_ops pr_mgmt_ops = { + .init = pr_mgmt_init, + .uinit = pr_mgmt_uinit, + .ioctl = fme_pr_ioctl, +}; diff --git a/drivers/fpga/dfl-fme-pr.h b/drivers/fpga/dfl-fme-pr.h new file mode 100644 index 000000000..096a69908 --- /dev/null +++ b/drivers/fpga/dfl-fme-pr.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Management Engine (FME) Partial Reconfiguration Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#ifndef __DFL_FME_PR_H +#define __DFL_FME_PR_H + +#include <linux/platform_device.h> + +/** + * struct dfl_fme_region - FME fpga region data structure + * + * @region: platform device of the FPGA region. + * @node: used to link fme_region to a list. + * @port_id: indicate which port this region connected to. + */ +struct dfl_fme_region { + struct platform_device *region; + struct list_head node; + int port_id; +}; + +/** + * struct dfl_fme_region_pdata - platform data for FME region platform device. + * + * @mgr: platform device of the FPGA manager. + * @br: platform device of the FPGA bridge. + * @region_id: region id (same as port_id). + */ +struct dfl_fme_region_pdata { + struct platform_device *mgr; + struct platform_device *br; + int region_id; +}; + +/** + * struct dfl_fme_bridge - FME fpga bridge data structure + * + * @br: platform device of the FPGA bridge. + * @node: used to link fme_bridge to a list. + */ +struct dfl_fme_bridge { + struct platform_device *br; + struct list_head node; +}; + +/** + * struct dfl_fme_bridge_pdata - platform data for FME bridge platform device. + * + * @cdev: container device. + * @port_id: port id. + */ +struct dfl_fme_br_pdata { + struct dfl_fpga_cdev *cdev; + int port_id; +}; + +/** + * struct dfl_fme_mgr_pdata - platform data for FME manager platform device. + * + * @ioaddr: mapped io address for FME manager platform device. + */ +struct dfl_fme_mgr_pdata { + void __iomem *ioaddr; +}; + +#define DFL_FPGA_FME_MGR "dfl-fme-mgr" +#define DFL_FPGA_FME_BRIDGE "dfl-fme-bridge" +#define DFL_FPGA_FME_REGION "dfl-fme-region" + +#endif /* __DFL_FME_PR_H */ diff --git a/drivers/fpga/dfl-fme-region.c b/drivers/fpga/dfl-fme-region.c new file mode 100644 index 000000000..51a5ac229 --- /dev/null +++ b/drivers/fpga/dfl-fme-region.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Region Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/module.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/fpga/fpga-region.h> + +#include "dfl-fme-pr.h" + +static int fme_region_get_bridges(struct fpga_region *region) +{ + struct dfl_fme_region_pdata *pdata = region->priv; + struct device *dev = &pdata->br->dev; + + return fpga_bridge_get_to_list(dev, region->info, ®ion->bridge_list); +} + +static int fme_region_probe(struct platform_device *pdev) +{ + struct dfl_fme_region_pdata *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct fpga_region *region; + struct fpga_manager *mgr; + int ret; + + mgr = fpga_mgr_get(&pdata->mgr->dev); + if (IS_ERR(mgr)) + return -EPROBE_DEFER; + + region = fpga_region_create(dev, mgr, fme_region_get_bridges); + if (!region) { + ret = -ENOMEM; + goto eprobe_mgr_put; + } + + region->priv = pdata; + region->compat_id = mgr->compat_id; + platform_set_drvdata(pdev, region); + + ret = fpga_region_register(region); + if (ret) + goto region_free; + + dev_dbg(dev, "DFL FME FPGA Region probed\n"); + + return 0; + +region_free: + fpga_region_free(region); +eprobe_mgr_put: + fpga_mgr_put(mgr); + return ret; +} + +static int fme_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = dev_get_drvdata(&pdev->dev); + struct fpga_manager *mgr = region->mgr; + + fpga_region_unregister(region); + fpga_mgr_put(mgr); + + return 0; +} + +static struct platform_driver fme_region_driver = { + .driver = { + .name = DFL_FPGA_FME_REGION, + }, + .probe = fme_region_probe, + .remove = fme_region_remove, +}; + +module_platform_driver(fme_region_driver); + +MODULE_DESCRIPTION("FPGA Region for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-region"); diff --git a/drivers/fpga/dfl-fme.h b/drivers/fpga/dfl-fme.h new file mode 100644 index 000000000..5394a216c --- /dev/null +++ b/drivers/fpga/dfl-fme.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Management Engine (FME) Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#ifndef __DFL_FME_H +#define __DFL_FME_H + +/** + * struct dfl_fme - dfl fme private data + * + * @mgr: FME's FPGA manager platform device. + * @region_list: linked list of FME's FPGA regions. + * @bridge_list: linked list of FME's FPGA bridges. + * @pdata: fme platform device's pdata. + */ +struct dfl_fme { + struct platform_device *mgr; + struct list_head region_list; + struct list_head bridge_list; + struct dfl_feature_platform_data *pdata; +}; + +extern const struct dfl_feature_ops pr_mgmt_ops; + +#endif /* __DFL_FME_H */ diff --git a/drivers/fpga/dfl-pci.c b/drivers/fpga/dfl-pci.c new file mode 100644 index 000000000..66b572058 --- /dev/null +++ b/drivers/fpga/dfl-pci.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Device Feature List (DFL) PCIe device + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Zhang Yi <Yi.Z.Zhang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/pci.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/stddef.h> +#include <linux/errno.h> +#include <linux/aer.h> + +#include "dfl.h" + +#define DRV_VERSION "0.8" +#define DRV_NAME "dfl-pci" + +struct cci_drvdata { + struct dfl_fpga_cdev *cdev; /* container device */ +}; + +static void __iomem *cci_pci_ioremap_bar(struct pci_dev *pcidev, int bar) +{ + if (pcim_iomap_regions(pcidev, BIT(bar), DRV_NAME)) + return NULL; + + return pcim_iomap_table(pcidev)[bar]; +} + +/* PCI Device ID */ +#define PCIE_DEVICE_ID_PF_INT_5_X 0xBCBD +#define PCIE_DEVICE_ID_PF_INT_6_X 0xBCC0 +#define PCIE_DEVICE_ID_PF_DSC_1_X 0x09C4 +/* VF Device */ +#define PCIE_DEVICE_ID_VF_INT_5_X 0xBCBF +#define PCIE_DEVICE_ID_VF_INT_6_X 0xBCC1 +#define PCIE_DEVICE_ID_VF_DSC_1_X 0x09C5 + +static struct pci_device_id cci_pcie_id_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_INT_5_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_INT_5_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_INT_6_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_INT_6_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_DSC_1_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_DSC_1_X),}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, cci_pcie_id_tbl); + +static int cci_init_drvdata(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata; + + drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + pci_set_drvdata(pcidev, drvdata); + + return 0; +} + +static void cci_remove_feature_devs(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata = pci_get_drvdata(pcidev); + + /* remove all children feature devices */ + dfl_fpga_feature_devs_remove(drvdata->cdev); +} + +/* enumerate feature devices under pci device */ +static int cci_enumerate_feature_devs(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata = pci_get_drvdata(pcidev); + struct dfl_fpga_enum_info *info; + struct dfl_fpga_cdev *cdev; + resource_size_t start, len; + int port_num, bar, i, ret = 0; + void __iomem *base; + u32 offset; + u64 v; + + /* allocate enumeration info via pci_dev */ + info = dfl_fpga_enum_info_alloc(&pcidev->dev); + if (!info) + return -ENOMEM; + + /* start to find Device Feature List from Bar 0 */ + base = cci_pci_ioremap_bar(pcidev, 0); + if (!base) { + ret = -ENOMEM; + goto enum_info_free_exit; + } + + /* + * PF device has FME and Ports/AFUs, and VF device only has one + * Port/AFU. Check them and add related "Device Feature List" info + * for the next step enumeration. + */ + if (dfl_feature_is_fme(base)) { + start = pci_resource_start(pcidev, 0); + len = pci_resource_len(pcidev, 0); + + dfl_fpga_enum_info_add_dfl(info, start, len, base); + + /* + * find more Device Feature Lists (e.g. Ports) per information + * indicated by FME module. + */ + v = readq(base + FME_HDR_CAP); + port_num = FIELD_GET(FME_CAP_NUM_PORTS, v); + + WARN_ON(port_num > MAX_DFL_FPGA_PORT_NUM); + + for (i = 0; i < port_num; i++) { + v = readq(base + FME_HDR_PORT_OFST(i)); + + /* skip ports which are not implemented. */ + if (!(v & FME_PORT_OFST_IMP)) + continue; + + /* + * add Port's Device Feature List information for next + * step enumeration. + */ + bar = FIELD_GET(FME_PORT_OFST_BAR_ID, v); + offset = FIELD_GET(FME_PORT_OFST_DFH_OFST, v); + base = cci_pci_ioremap_bar(pcidev, bar); + if (!base) + continue; + + start = pci_resource_start(pcidev, bar) + offset; + len = pci_resource_len(pcidev, bar) - offset; + + dfl_fpga_enum_info_add_dfl(info, start, len, + base + offset); + } + } else if (dfl_feature_is_port(base)) { + start = pci_resource_start(pcidev, 0); + len = pci_resource_len(pcidev, 0); + + dfl_fpga_enum_info_add_dfl(info, start, len, base); + } else { + ret = -ENODEV; + goto enum_info_free_exit; + } + + /* start enumeration with prepared enumeration information */ + cdev = dfl_fpga_feature_devs_enumerate(info); + if (IS_ERR(cdev)) { + dev_err(&pcidev->dev, "Enumeration failure\n"); + ret = PTR_ERR(cdev); + goto enum_info_free_exit; + } + + drvdata->cdev = cdev; + +enum_info_free_exit: + dfl_fpga_enum_info_free(info); + + return ret; +} + +static +int cci_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *pcidevid) +{ + int ret; + + ret = pcim_enable_device(pcidev); + if (ret < 0) { + dev_err(&pcidev->dev, "Failed to enable device %d.\n", ret); + return ret; + } + + ret = pci_enable_pcie_error_reporting(pcidev); + if (ret && ret != -EINVAL) + dev_info(&pcidev->dev, "PCIE AER unavailable %d.\n", ret); + + pci_set_master(pcidev); + + if (!pci_set_dma_mask(pcidev, DMA_BIT_MASK(64))) { + ret = pci_set_consistent_dma_mask(pcidev, DMA_BIT_MASK(64)); + if (ret) + goto disable_error_report_exit; + } else if (!pci_set_dma_mask(pcidev, DMA_BIT_MASK(32))) { + ret = pci_set_consistent_dma_mask(pcidev, DMA_BIT_MASK(32)); + if (ret) + goto disable_error_report_exit; + } else { + ret = -EIO; + dev_err(&pcidev->dev, "No suitable DMA support available.\n"); + goto disable_error_report_exit; + } + + ret = cci_init_drvdata(pcidev); + if (ret) { + dev_err(&pcidev->dev, "Fail to init drvdata %d.\n", ret); + goto disable_error_report_exit; + } + + ret = cci_enumerate_feature_devs(pcidev); + if (ret) { + dev_err(&pcidev->dev, "enumeration failure %d.\n", ret); + goto disable_error_report_exit; + } + + return ret; + +disable_error_report_exit: + pci_disable_pcie_error_reporting(pcidev); + return ret; +} + +static void cci_pci_remove(struct pci_dev *pcidev) +{ + cci_remove_feature_devs(pcidev); + pci_disable_pcie_error_reporting(pcidev); +} + +static struct pci_driver cci_pci_driver = { + .name = DRV_NAME, + .id_table = cci_pcie_id_tbl, + .probe = cci_pci_probe, + .remove = cci_pci_remove, +}; + +module_pci_driver(cci_pci_driver); + +MODULE_DESCRIPTION("FPGA DFL PCIe Device Driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c new file mode 100644 index 000000000..ab361ec78 --- /dev/null +++ b/drivers/fpga/dfl.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Device Feature List (DFL) Support + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Zhang Yi <yi.z.zhang@intel.com> + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ +#include <linux/module.h> + +#include "dfl.h" + +static DEFINE_MUTEX(dfl_id_mutex); + +/* + * when adding a new feature dev support in DFL framework, it's required to + * add a new item in enum dfl_id_type and provide related information in below + * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for + * platform device creation (define name strings in dfl.h, as they could be + * reused by platform device drivers). + * + * if the new feature dev needs chardev support, then it's required to add + * a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as + * index to dfl_chardevs table. If no chardev support just set devt_type + * as one invalid index (DFL_FPGA_DEVT_MAX). + */ +enum dfl_id_type { + FME_ID, /* fme id allocation and mapping */ + PORT_ID, /* port id allocation and mapping */ + DFL_ID_MAX, +}; + +enum dfl_fpga_devt_type { + DFL_FPGA_DEVT_FME, + DFL_FPGA_DEVT_PORT, + DFL_FPGA_DEVT_MAX, +}; + +static struct lock_class_key dfl_pdata_keys[DFL_ID_MAX]; + +static const char *dfl_pdata_key_strings[DFL_ID_MAX] = { + "dfl-fme-pdata", + "dfl-port-pdata", +}; + +/** + * dfl_dev_info - dfl feature device information. + * @name: name string of the feature platform device. + * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. + * @id: idr id of the feature dev. + * @devt_type: index to dfl_chrdevs[]. + */ +struct dfl_dev_info { + const char *name; + u32 dfh_id; + struct idr id; + enum dfl_fpga_devt_type devt_type; +}; + +/* it is indexed by dfl_id_type */ +static struct dfl_dev_info dfl_devs[] = { + {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME, + .devt_type = DFL_FPGA_DEVT_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT, + .devt_type = DFL_FPGA_DEVT_PORT}, +}; + +/** + * dfl_chardev_info - chardev information of dfl feature device + * @name: nmae string of the char device. + * @devt: devt of the char device. + */ +struct dfl_chardev_info { + const char *name; + dev_t devt; +}; + +/* indexed by enum dfl_fpga_devt_type */ +static struct dfl_chardev_info dfl_chrdevs[] = { + {.name = DFL_FPGA_FEATURE_DEV_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT}, +}; + +static void dfl_ids_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + idr_init(&dfl_devs[i].id); +} + +static void dfl_ids_destroy(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + idr_destroy(&dfl_devs[i].id); +} + +static int dfl_id_alloc(enum dfl_id_type type, struct device *dev) +{ + int id; + + WARN_ON(type >= DFL_ID_MAX); + mutex_lock(&dfl_id_mutex); + id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL); + mutex_unlock(&dfl_id_mutex); + + return id; +} + +static void dfl_id_free(enum dfl_id_type type, int id) +{ + WARN_ON(type >= DFL_ID_MAX); + mutex_lock(&dfl_id_mutex); + idr_remove(&dfl_devs[type].id, id); + mutex_unlock(&dfl_id_mutex); +} + +static enum dfl_id_type feature_dev_id_type(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + if (!strcmp(dfl_devs[i].name, pdev->name)) + return i; + + return DFL_ID_MAX; +} + +static enum dfl_id_type dfh_id_to_type(u32 id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + if (dfl_devs[i].dfh_id == id) + return i; + + return DFL_ID_MAX; +} + +/* + * introduce a global port_ops list, it allows port drivers to register ops + * in such list, then other feature devices (e.g. FME), could use the port + * functions even related port platform device is hidden. Below is one example, + * in virtualization case of PCIe-based FPGA DFL device, when SRIOV is + * enabled, port (and it's AFU) is turned into VF and port platform device + * is hidden from system but it's still required to access port to finish FPGA + * reconfiguration function in FME. + */ + +static DEFINE_MUTEX(dfl_port_ops_mutex); +static LIST_HEAD(dfl_port_ops_list); + +/** + * dfl_fpga_port_ops_get - get matched port ops from the global list + * @pdev: platform device to match with associated port ops. + * Return: matched port ops on success, NULL otherwise. + * + * Please note that must dfl_fpga_port_ops_put after use the port_ops. + */ +struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev) +{ + struct dfl_fpga_port_ops *ops = NULL; + + mutex_lock(&dfl_port_ops_mutex); + if (list_empty(&dfl_port_ops_list)) + goto done; + + list_for_each_entry(ops, &dfl_port_ops_list, node) { + /* match port_ops using the name of platform device */ + if (!strcmp(pdev->name, ops->name)) { + if (!try_module_get(ops->owner)) + ops = NULL; + goto done; + } + } + + ops = NULL; +done: + mutex_unlock(&dfl_port_ops_mutex); + return ops; +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_get); + +/** + * dfl_fpga_port_ops_put - put port ops + * @ops: port ops. + */ +void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops) +{ + if (ops && ops->owner) + module_put(ops->owner); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_put); + +/** + * dfl_fpga_port_ops_add - add port_ops to global list + * @ops: port ops to add. + */ +void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops) +{ + mutex_lock(&dfl_port_ops_mutex); + list_add_tail(&ops->node, &dfl_port_ops_list); + mutex_unlock(&dfl_port_ops_mutex); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_add); + +/** + * dfl_fpga_port_ops_del - remove port_ops from global list + * @ops: port ops to del. + */ +void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops) +{ + mutex_lock(&dfl_port_ops_mutex); + list_del(&ops->node); + mutex_unlock(&dfl_port_ops_mutex); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); + +/** + * dfl_fpga_check_port_id - check the port id + * @pdev: port platform device. + * @pport_id: port id to compare. + * + * Return: 1 if port device matches with given port id, otherwise 0. + */ +int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id) +{ + struct dfl_fpga_port_ops *port_ops = dfl_fpga_port_ops_get(pdev); + int port_id; + + if (!port_ops || !port_ops->get_id) + return 0; + + port_id = port_ops->get_id(pdev); + dfl_fpga_port_ops_put(port_ops); + + return port_id == *(int *)pport_id; +} +EXPORT_SYMBOL_GPL(dfl_fpga_check_port_id); + +/** + * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device + * @pdev: feature device. + */ +void dfl_fpga_dev_feature_uinit(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) + if (feature->ops) { + feature->ops->uinit(pdev, feature); + feature->ops = NULL; + } +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_uinit); + +static int dfl_feature_instance_init(struct platform_device *pdev, + struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature, + struct dfl_feature_driver *drv) +{ + int ret; + + ret = drv->ops->init(pdev, feature); + if (ret) + return ret; + + feature->ops = drv->ops; + + return ret; +} + +/** + * dfl_fpga_dev_feature_init - init for sub features of dfl feature device + * @pdev: feature device. + * @feature_drvs: drvs for sub features. + * + * This function will match sub features with given feature drvs list and + * use matched drv to init related sub feature. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_dev_feature_init(struct platform_device *pdev, + struct dfl_feature_driver *feature_drvs) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_feature_driver *drv = feature_drvs; + struct dfl_feature *feature; + int ret; + + while (drv->ops) { + dfl_fpga_dev_for_each_feature(pdata, feature) { + /* match feature and drv using id */ + if (feature->id == drv->id) { + ret = dfl_feature_instance_init(pdev, pdata, + feature, drv); + if (ret) + goto exit; + } + } + drv++; + } + + return 0; +exit: + dfl_fpga_dev_feature_uinit(pdev); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_init); + +static void dfl_chardev_uinit(void) +{ + int i; + + for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) + if (MAJOR(dfl_chrdevs[i].devt)) { + unregister_chrdev_region(dfl_chrdevs[i].devt, + MINORMASK); + dfl_chrdevs[i].devt = MKDEV(0, 0); + } +} + +static int dfl_chardev_init(void) +{ + int i, ret; + + for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) { + ret = alloc_chrdev_region(&dfl_chrdevs[i].devt, 0, MINORMASK, + dfl_chrdevs[i].name); + if (ret) + goto exit; + } + + return 0; + +exit: + dfl_chardev_uinit(); + return ret; +} + +static dev_t dfl_get_devt(enum dfl_fpga_devt_type type, int id) +{ + if (type >= DFL_FPGA_DEVT_MAX) + return 0; + + return MKDEV(MAJOR(dfl_chrdevs[type].devt), id); +} + +/** + * dfl_fpga_dev_ops_register - register cdev ops for feature dev + * + * @pdev: feature dev. + * @fops: file operations for feature dev's cdev. + * @owner: owning module/driver. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_dev_ops_register(struct platform_device *pdev, + const struct file_operations *fops, + struct module *owner) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + + cdev_init(&pdata->cdev, fops); + pdata->cdev.owner = owner; + + /* + * set parent to the feature device so that its refcount is + * decreased after the last refcount of cdev is gone, that + * makes sure the feature device is valid during device + * file's life-cycle. + */ + pdata->cdev.kobj.parent = &pdev->dev.kobj; + + return cdev_add(&pdata->cdev, pdev->dev.devt, 1); +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_register); + +/** + * dfl_fpga_dev_ops_unregister - unregister cdev ops for feature dev + * @pdev: feature dev. + */ +void dfl_fpga_dev_ops_unregister(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + + cdev_del(&pdata->cdev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_unregister); + +/** + * struct build_feature_devs_info - info collected during feature dev build. + * + * @dev: device to enumerate. + * @cdev: the container device for all feature devices. + * @feature_dev: current feature device. + * @ioaddr: header register region address of feature device in enumeration. + * @sub_features: a sub features linked list for feature device in enumeration. + * @feature_num: number of sub features for feature device in enumeration. + */ +struct build_feature_devs_info { + struct device *dev; + struct dfl_fpga_cdev *cdev; + struct platform_device *feature_dev; + void __iomem *ioaddr; + struct list_head sub_features; + int feature_num; +}; + +/** + * struct dfl_feature_info - sub feature info collected during feature dev build + * + * @fid: id of this sub feature. + * @mmio_res: mmio resource of this sub feature. + * @ioaddr: mapped base address of mmio resource. + * @node: node in sub_features linked list. + */ +struct dfl_feature_info { + u64 fid; + struct resource mmio_res; + void __iomem *ioaddr; + struct list_head node; +}; + +static void dfl_fpga_cdev_add_port_dev(struct dfl_fpga_cdev *cdev, + struct platform_device *port) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&port->dev); + + mutex_lock(&cdev->lock); + list_add(&pdata->node, &cdev->port_dev_list); + get_device(&pdata->dev->dev); + mutex_unlock(&cdev->lock); +} + +/* + * register current feature device, it is called when we need to switch to + * another feature parsing or we have parsed all features on given device + * feature list. + */ +static int build_info_commit_dev(struct build_feature_devs_info *binfo) +{ + struct platform_device *fdev = binfo->feature_dev; + struct dfl_feature_platform_data *pdata; + struct dfl_feature_info *finfo, *p; + enum dfl_id_type type; + int ret, index = 0; + + if (!fdev) + return 0; + + type = feature_dev_id_type(fdev); + if (WARN_ON_ONCE(type >= DFL_ID_MAX)) + return -EINVAL; + + /* + * we do not need to care for the memory which is associated with + * the platform device. After calling platform_device_unregister(), + * it will be automatically freed by device's release() callback, + * platform_device_release(). + */ + pdata = kzalloc(dfl_feature_platform_data_size(binfo->feature_num), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->dev = fdev; + pdata->num = binfo->feature_num; + pdata->dfl_cdev = binfo->cdev; + mutex_init(&pdata->lock); + lockdep_set_class_and_name(&pdata->lock, &dfl_pdata_keys[type], + dfl_pdata_key_strings[type]); + + /* + * the count should be initialized to 0 to make sure + *__fpga_port_enable() following __fpga_port_disable() + * works properly for port device. + * and it should always be 0 for fme device. + */ + WARN_ON(pdata->disable_count); + + fdev->dev.platform_data = pdata; + + /* each sub feature has one MMIO resource */ + fdev->num_resources = binfo->feature_num; + fdev->resource = kcalloc(binfo->feature_num, sizeof(*fdev->resource), + GFP_KERNEL); + if (!fdev->resource) + return -ENOMEM; + + /* fill features and resource information for feature dev */ + list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { + struct dfl_feature *feature = &pdata->features[index]; + + /* save resource information for each feature */ + feature->id = finfo->fid; + feature->resource_index = index; + feature->ioaddr = finfo->ioaddr; + fdev->resource[index++] = finfo->mmio_res; + + list_del(&finfo->node); + kfree(finfo); + } + + ret = platform_device_add(binfo->feature_dev); + if (!ret) { + if (type == PORT_ID) + dfl_fpga_cdev_add_port_dev(binfo->cdev, + binfo->feature_dev); + else + binfo->cdev->fme_dev = + get_device(&binfo->feature_dev->dev); + /* + * reset it to avoid build_info_free() freeing their resource. + * + * The resource of successfully registered feature devices + * will be freed by platform_device_unregister(). See the + * comments in build_info_create_dev(). + */ + binfo->feature_dev = NULL; + } + + return ret; +} + +static int +build_info_create_dev(struct build_feature_devs_info *binfo, + enum dfl_id_type type, void __iomem *ioaddr) +{ + struct platform_device *fdev; + int ret; + + if (type >= DFL_ID_MAX) + return -EINVAL; + + /* we will create a new device, commit current device first */ + ret = build_info_commit_dev(binfo); + if (ret) + return ret; + + /* + * we use -ENODEV as the initialization indicator which indicates + * whether the id need to be reclaimed + */ + fdev = platform_device_alloc(dfl_devs[type].name, -ENODEV); + if (!fdev) + return -ENOMEM; + + binfo->feature_dev = fdev; + binfo->feature_num = 0; + binfo->ioaddr = ioaddr; + INIT_LIST_HEAD(&binfo->sub_features); + + fdev->id = dfl_id_alloc(type, &fdev->dev); + if (fdev->id < 0) + return fdev->id; + + fdev->dev.parent = &binfo->cdev->region->dev; + fdev->dev.devt = dfl_get_devt(dfl_devs[type].devt_type, fdev->id); + + return 0; +} + +static void build_info_free(struct build_feature_devs_info *binfo) +{ + struct dfl_feature_info *finfo, *p; + + /* + * it is a valid id, free it. See comments in + * build_info_create_dev() + */ + if (binfo->feature_dev && binfo->feature_dev->id >= 0) { + dfl_id_free(feature_dev_id_type(binfo->feature_dev), + binfo->feature_dev->id); + + list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { + list_del(&finfo->node); + kfree(finfo); + } + } + + platform_device_put(binfo->feature_dev); + + devm_kfree(binfo->dev, binfo); +} + +static inline u32 feature_size(void __iomem *start) +{ + u64 v = readq(start + DFH); + u32 ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); + /* workaround for private features with invalid size, use 4K instead */ + return ofst ? ofst : 4096; +} + +static u64 feature_id(void __iomem *start) +{ + u64 v = readq(start + DFH); + u16 id = FIELD_GET(DFH_ID, v); + u8 type = FIELD_GET(DFH_TYPE, v); + + if (type == DFH_TYPE_FIU) + return FEATURE_ID_FIU_HEADER; + else if (type == DFH_TYPE_PRIVATE) + return id; + else if (type == DFH_TYPE_AFU) + return FEATURE_ID_AFU; + + WARN_ON(1); + return 0; +} + +/* + * when create sub feature instances, for private features, it doesn't need + * to provide resource size and feature id as they could be read from DFH + * register. For afu sub feature, its register region only contains user + * defined registers, so never trust any information from it, just use the + * resource size information provided by its parent FIU. + */ +static int +create_feature_instance(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst, + resource_size_t size, u64 fid) +{ + struct dfl_feature_info *finfo; + + /* read feature size and id if inputs are invalid */ + size = size ? size : feature_size(dfl->ioaddr + ofst); + fid = fid ? fid : feature_id(dfl->ioaddr + ofst); + + if (dfl->len - ofst < size) + return -EINVAL; + + finfo = kzalloc(sizeof(*finfo), GFP_KERNEL); + if (!finfo) + return -ENOMEM; + + finfo->fid = fid; + finfo->mmio_res.start = dfl->start + ofst; + finfo->mmio_res.end = finfo->mmio_res.start + size - 1; + finfo->mmio_res.flags = IORESOURCE_MEM; + finfo->ioaddr = dfl->ioaddr + ofst; + + list_add_tail(&finfo->node, &binfo->sub_features); + binfo->feature_num++; + + return 0; +} + +static int parse_feature_port_afu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + u64 v = readq(binfo->ioaddr + PORT_HDR_CAP); + u32 size = FIELD_GET(PORT_CAP_MMIO_SIZE, v) << 10; + + WARN_ON(!size); + + return create_feature_instance(binfo, dfl, ofst, size, FEATURE_ID_AFU); +} + +static int parse_feature_afu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + if (!binfo->feature_dev) { + dev_err(binfo->dev, "this AFU does not belong to any FIU.\n"); + return -EINVAL; + } + + switch (feature_dev_id_type(binfo->feature_dev)) { + case PORT_ID: + return parse_feature_port_afu(binfo, dfl, ofst); + default: + dev_info(binfo->dev, "AFU belonging to FIU %s is not supported yet.\n", + binfo->feature_dev->name); + } + + return 0; +} + +static int parse_feature_fiu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + u32 id, offset; + u64 v; + int ret = 0; + + v = readq(dfl->ioaddr + ofst + DFH); + id = FIELD_GET(DFH_ID, v); + + /* create platform device for dfl feature dev */ + ret = build_info_create_dev(binfo, dfh_id_to_type(id), + dfl->ioaddr + ofst); + if (ret) + return ret; + + ret = create_feature_instance(binfo, dfl, ofst, 0, 0); + if (ret) + return ret; + /* + * find and parse FIU's child AFU via its NEXT_AFU register. + * please note that only Port has valid NEXT_AFU pointer per spec. + */ + v = readq(dfl->ioaddr + ofst + NEXT_AFU); + + offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v); + if (offset) + return parse_feature_afu(binfo, dfl, ofst + offset); + + dev_dbg(binfo->dev, "No AFUs detected on FIU %d\n", id); + + return ret; +} + +static int parse_feature_private(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + if (!binfo->feature_dev) { + dev_err(binfo->dev, "the private feature %llx does not belong to any AFU.\n", + (unsigned long long)feature_id(dfl->ioaddr + ofst)); + return -EINVAL; + } + + return create_feature_instance(binfo, dfl, ofst, 0, 0); +} + +/** + * parse_feature - parse a feature on given device feature list + * + * @binfo: build feature devices information. + * @dfl: device feature list to parse + * @ofst: offset to feature header on this device feature list + */ +static int parse_feature(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) +{ + u64 v; + u32 type; + + v = readq(dfl->ioaddr + ofst + DFH); + type = FIELD_GET(DFH_TYPE, v); + + switch (type) { + case DFH_TYPE_AFU: + return parse_feature_afu(binfo, dfl, ofst); + case DFH_TYPE_PRIVATE: + return parse_feature_private(binfo, dfl, ofst); + case DFH_TYPE_FIU: + return parse_feature_fiu(binfo, dfl, ofst); + default: + dev_info(binfo->dev, + "Feature Type %x is not supported.\n", type); + } + + return 0; +} + +static int parse_feature_list(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl) +{ + void __iomem *start = dfl->ioaddr; + void __iomem *end = dfl->ioaddr + dfl->len; + int ret = 0; + u32 ofst = 0; + u64 v; + + /* walk through the device feature list via DFH's next DFH pointer. */ + for (; start < end; start += ofst) { + if (end - start < DFH_SIZE) { + dev_err(binfo->dev, "The region is too small to contain a feature.\n"); + return -EINVAL; + } + + ret = parse_feature(binfo, dfl, start - dfl->ioaddr); + if (ret) + return ret; + + v = readq(start + DFH); + ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); + + /* stop parsing if EOL(End of List) is set or offset is 0 */ + if ((v & DFH_EOL) || !ofst) + break; + } + + /* commit current feature device when reach the end of list */ + return build_info_commit_dev(binfo); +} + +struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev) +{ + struct dfl_fpga_enum_info *info; + + get_device(dev); + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) { + put_device(dev); + return NULL; + } + + info->dev = dev; + INIT_LIST_HEAD(&info->dfls); + + return info; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_alloc); + +void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info) +{ + struct dfl_fpga_enum_dfl *tmp, *dfl; + struct device *dev; + + if (!info) + return; + + dev = info->dev; + + /* remove all device feature lists in the list. */ + list_for_each_entry_safe(dfl, tmp, &info->dfls, node) { + list_del(&dfl->node); + devm_kfree(dev, dfl); + } + + devm_kfree(dev, info); + put_device(dev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_free); + +/** + * dfl_fpga_enum_info_add_dfl - add info of a device feature list to enum info + * + * @info: ptr to dfl_fpga_enum_info + * @start: mmio resource address of the device feature list. + * @len: mmio resource length of the device feature list. + * @ioaddr: mapped mmio resource address of the device feature list. + * + * One FPGA device may have one or more Device Feature Lists (DFLs), use this + * function to add information of each DFL to common data structure for next + * step enumeration. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, + resource_size_t start, resource_size_t len, + void __iomem *ioaddr) +{ + struct dfl_fpga_enum_dfl *dfl; + + dfl = devm_kzalloc(info->dev, sizeof(*dfl), GFP_KERNEL); + if (!dfl) + return -ENOMEM; + + dfl->start = start; + dfl->len = len; + dfl->ioaddr = ioaddr; + + list_add_tail(&dfl->node, &info->dfls); + + return 0; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_dfl); + +static int remove_feature_dev(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + enum dfl_id_type type = feature_dev_id_type(pdev); + int id = pdev->id; + + platform_device_unregister(pdev); + + dfl_id_free(type, id); + + return 0; +} + +static void remove_feature_devs(struct dfl_fpga_cdev *cdev) +{ + device_for_each_child(&cdev->region->dev, NULL, remove_feature_dev); +} + +/** + * dfl_fpga_feature_devs_enumerate - enumerate feature devices + * @info: information for enumeration. + * + * This function creates a container device (base FPGA region), enumerates + * feature devices based on the enumeration info and creates platform devices + * under the container device. + * + * Return: dfl_fpga_cdev struct on success, -errno on failure + */ +struct dfl_fpga_cdev * +dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info) +{ + struct build_feature_devs_info *binfo; + struct dfl_fpga_enum_dfl *dfl; + struct dfl_fpga_cdev *cdev; + int ret = 0; + + if (!info->dev) + return ERR_PTR(-ENODEV); + + cdev = devm_kzalloc(info->dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return ERR_PTR(-ENOMEM); + + cdev->region = fpga_region_create(info->dev, NULL, NULL); + if (!cdev->region) { + ret = -ENOMEM; + goto free_cdev_exit; + } + + cdev->parent = info->dev; + mutex_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->port_dev_list); + + ret = fpga_region_register(cdev->region); + if (ret) + goto free_region_exit; + + /* create and init build info for enumeration */ + binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL); + if (!binfo) { + ret = -ENOMEM; + goto unregister_region_exit; + } + + binfo->dev = info->dev; + binfo->cdev = cdev; + + /* + * start enumeration for all feature devices based on Device Feature + * Lists. + */ + list_for_each_entry(dfl, &info->dfls, node) { + ret = parse_feature_list(binfo, dfl); + if (ret) { + remove_feature_devs(cdev); + build_info_free(binfo); + goto unregister_region_exit; + } + } + + build_info_free(binfo); + + return cdev; + +unregister_region_exit: + fpga_region_unregister(cdev->region); +free_region_exit: + fpga_region_free(cdev->region); +free_cdev_exit: + devm_kfree(info->dev, cdev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_enumerate); + +/** + * dfl_fpga_feature_devs_remove - remove all feature devices + * @cdev: fpga container device. + * + * Remove the container device and all feature devices under given container + * devices. + */ +void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev) +{ + struct dfl_feature_platform_data *pdata, *ptmp; + + remove_feature_devs(cdev); + + mutex_lock(&cdev->lock); + if (cdev->fme_dev) { + /* the fme should be unregistered. */ + WARN_ON(device_is_registered(cdev->fme_dev)); + put_device(cdev->fme_dev); + } + + list_for_each_entry_safe(pdata, ptmp, &cdev->port_dev_list, node) { + struct platform_device *port_dev = pdata->dev; + + /* the port should be unregistered. */ + WARN_ON(device_is_registered(&port_dev->dev)); + list_del(&pdata->node); + put_device(&port_dev->dev); + } + mutex_unlock(&cdev->lock); + + fpga_region_unregister(cdev->region); + devm_kfree(cdev->parent, cdev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_remove); + +/** + * __dfl_fpga_cdev_find_port - find a port under given container device + * + * @cdev: container device + * @data: data passed to match function + * @match: match function used to find specific port from the port device list + * + * Find a port device under container device. This function needs to be + * invoked with lock held. + * + * Return: pointer to port's platform device if successful, NULL otherwise. + * + * NOTE: you will need to drop the device reference with put_device() after use. + */ +struct platform_device * +__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)) +{ + struct dfl_feature_platform_data *pdata; + struct platform_device *port_dev; + + list_for_each_entry(pdata, &cdev->port_dev_list, node) { + port_dev = pdata->dev; + + if (match(port_dev, data) && get_device(&port_dev->dev)) + return port_dev; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(__dfl_fpga_cdev_find_port); + +static int __init dfl_fpga_init(void) +{ + int ret; + + dfl_ids_init(); + + ret = dfl_chardev_init(); + if (ret) + dfl_ids_destroy(); + + return ret; +} + +static void __exit dfl_fpga_exit(void) +{ + dfl_chardev_uinit(); + dfl_ids_destroy(); +} + +module_init(dfl_fpga_init); +module_exit(dfl_fpga_exit); + +MODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h new file mode 100644 index 000000000..a8b869e9e --- /dev/null +++ b/drivers/fpga/dfl.h @@ -0,0 +1,410 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver Header File for FPGA Device Feature List (DFL) Support + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Zhang Yi <yi.z.zhang@intel.com> + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ + +#ifndef __FPGA_DFL_H +#define __FPGA_DFL_H + +#include <linux/bitfield.h> +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/iopoll.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uuid.h> +#include <linux/fpga/fpga-region.h> + +/* maximum supported number of ports */ +#define MAX_DFL_FPGA_PORT_NUM 4 +/* plus one for fme device */ +#define MAX_DFL_FEATURE_DEV_NUM (MAX_DFL_FPGA_PORT_NUM + 1) + +/* Reserved 0x0 for Header Group Register and 0xff for AFU */ +#define FEATURE_ID_FIU_HEADER 0x0 +#define FEATURE_ID_AFU 0xff + +#define FME_FEATURE_ID_HEADER FEATURE_ID_FIU_HEADER +#define FME_FEATURE_ID_THERMAL_MGMT 0x1 +#define FME_FEATURE_ID_POWER_MGMT 0x2 +#define FME_FEATURE_ID_GLOBAL_IPERF 0x3 +#define FME_FEATURE_ID_GLOBAL_ERR 0x4 +#define FME_FEATURE_ID_PR_MGMT 0x5 +#define FME_FEATURE_ID_HSSI 0x6 +#define FME_FEATURE_ID_GLOBAL_DPERF 0x7 + +#define PORT_FEATURE_ID_HEADER FEATURE_ID_FIU_HEADER +#define PORT_FEATURE_ID_AFU FEATURE_ID_AFU +#define PORT_FEATURE_ID_ERROR 0x10 +#define PORT_FEATURE_ID_UMSG 0x11 +#define PORT_FEATURE_ID_UINT 0x12 +#define PORT_FEATURE_ID_STP 0x13 + +/* + * Device Feature Header Register Set + * + * For FIUs, they all have DFH + GUID + NEXT_AFU as common header registers. + * For AFUs, they have DFH + GUID as common header registers. + * For private features, they only have DFH register as common header. + */ +#define DFH 0x0 +#define GUID_L 0x8 +#define GUID_H 0x10 +#define NEXT_AFU 0x18 + +#define DFH_SIZE 0x8 + +/* Device Feature Header Register Bitfield */ +#define DFH_ID GENMASK_ULL(11, 0) /* Feature ID */ +#define DFH_ID_FIU_FME 0 +#define DFH_ID_FIU_PORT 1 +#define DFH_REVISION GENMASK_ULL(15, 12) /* Feature revision */ +#define DFH_NEXT_HDR_OFST GENMASK_ULL(39, 16) /* Offset to next DFH */ +#define DFH_EOL BIT_ULL(40) /* End of list */ +#define DFH_TYPE GENMASK_ULL(63, 60) /* Feature type */ +#define DFH_TYPE_AFU 1 +#define DFH_TYPE_PRIVATE 3 +#define DFH_TYPE_FIU 4 + +/* Next AFU Register Bitfield */ +#define NEXT_AFU_NEXT_DFH_OFST GENMASK_ULL(23, 0) /* Offset to next AFU */ + +/* FME Header Register Set */ +#define FME_HDR_DFH DFH +#define FME_HDR_GUID_L GUID_L +#define FME_HDR_GUID_H GUID_H +#define FME_HDR_NEXT_AFU NEXT_AFU +#define FME_HDR_CAP 0x30 +#define FME_HDR_PORT_OFST(n) (0x38 + ((n) * 0x8)) +#define FME_HDR_BITSTREAM_ID 0x60 +#define FME_HDR_BITSTREAM_MD 0x68 + +/* FME Fab Capability Register Bitfield */ +#define FME_CAP_FABRIC_VERID GENMASK_ULL(7, 0) /* Fabric version ID */ +#define FME_CAP_SOCKET_ID BIT_ULL(8) /* Socket ID */ +#define FME_CAP_PCIE0_LINK_AVL BIT_ULL(12) /* PCIE0 Link */ +#define FME_CAP_PCIE1_LINK_AVL BIT_ULL(13) /* PCIE1 Link */ +#define FME_CAP_COHR_LINK_AVL BIT_ULL(14) /* Coherent Link */ +#define FME_CAP_IOMMU_AVL BIT_ULL(16) /* IOMMU available */ +#define FME_CAP_NUM_PORTS GENMASK_ULL(19, 17) /* Number of ports */ +#define FME_CAP_ADDR_WIDTH GENMASK_ULL(29, 24) /* Address bus width */ +#define FME_CAP_CACHE_SIZE GENMASK_ULL(43, 32) /* cache size in KB */ +#define FME_CAP_CACHE_ASSOC GENMASK_ULL(47, 44) /* Associativity */ + +/* FME Port Offset Register Bitfield */ +/* Offset to port device feature header */ +#define FME_PORT_OFST_DFH_OFST GENMASK_ULL(23, 0) +/* PCI Bar ID for this port */ +#define FME_PORT_OFST_BAR_ID GENMASK_ULL(34, 32) +/* AFU MMIO access permission. 1 - VF, 0 - PF. */ +#define FME_PORT_OFST_ACC_CTRL BIT_ULL(55) +#define FME_PORT_OFST_ACC_PF 0 +#define FME_PORT_OFST_ACC_VF 1 +#define FME_PORT_OFST_IMP BIT_ULL(60) + +/* PORT Header Register Set */ +#define PORT_HDR_DFH DFH +#define PORT_HDR_GUID_L GUID_L +#define PORT_HDR_GUID_H GUID_H +#define PORT_HDR_NEXT_AFU NEXT_AFU +#define PORT_HDR_CAP 0x30 +#define PORT_HDR_CTRL 0x38 + +/* Port Capability Register Bitfield */ +#define PORT_CAP_PORT_NUM GENMASK_ULL(1, 0) /* ID of this port */ +#define PORT_CAP_MMIO_SIZE GENMASK_ULL(23, 8) /* MMIO size in KB */ +#define PORT_CAP_SUPP_INT_NUM GENMASK_ULL(35, 32) /* Interrupts num */ + +/* Port Control Register Bitfield */ +#define PORT_CTRL_SFTRST BIT_ULL(0) /* Port soft reset */ +/* Latency tolerance reporting. '1' >= 40us, '0' < 40us.*/ +#define PORT_CTRL_LATENCY BIT_ULL(2) +#define PORT_CTRL_SFTRST_ACK BIT_ULL(4) /* HW ack for reset */ +/** + * struct dfl_fpga_port_ops - port ops + * + * @name: name of this port ops, to match with port platform device. + * @owner: pointer to the module which owns this port ops. + * @node: node to link port ops to global list. + * @get_id: get port id from hardware. + * @enable_set: enable/disable the port. + */ +struct dfl_fpga_port_ops { + const char *name; + struct module *owner; + struct list_head node; + int (*get_id)(struct platform_device *pdev); + int (*enable_set)(struct platform_device *pdev, bool enable); +}; + +void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops); +void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops); +struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev); +void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops); +int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id); + +/** + * struct dfl_feature_driver - sub feature's driver + * + * @id: sub feature id. + * @ops: ops of this sub feature. + */ +struct dfl_feature_driver { + u64 id; + const struct dfl_feature_ops *ops; +}; + +/** + * struct dfl_feature - sub feature of the feature devices + * + * @id: sub feature id. + * @resource_index: each sub feature has one mmio resource for its registers. + * this index is used to find its mmio resource from the + * feature dev (platform device)'s reources. + * @ioaddr: mapped mmio resource address. + * @ops: ops of this sub feature. + */ +struct dfl_feature { + u64 id; + int resource_index; + void __iomem *ioaddr; + const struct dfl_feature_ops *ops; +}; + +#define DEV_STATUS_IN_USE 0 + +/** + * struct dfl_feature_platform_data - platform data for feature devices + * + * @node: node to link feature devs to container device's port_dev_list. + * @lock: mutex to protect platform data. + * @cdev: cdev of feature dev. + * @dev: ptr to platform device linked with this platform data. + * @dfl_cdev: ptr to container device. + * @disable_count: count for port disable. + * @num: number for sub features. + * @dev_status: dev status (e.g. DEV_STATUS_IN_USE). + * @private: ptr to feature dev private data. + * @features: sub features of this feature dev. + */ +struct dfl_feature_platform_data { + struct list_head node; + struct mutex lock; + struct cdev cdev; + struct platform_device *dev; + struct dfl_fpga_cdev *dfl_cdev; + unsigned int disable_count; + unsigned long dev_status; + void *private; + int num; + struct dfl_feature features[0]; +}; + +static inline +int dfl_feature_dev_use_begin(struct dfl_feature_platform_data *pdata) +{ + /* Test and set IN_USE flags to ensure file is exclusively used */ + if (test_and_set_bit_lock(DEV_STATUS_IN_USE, &pdata->dev_status)) + return -EBUSY; + + return 0; +} + +static inline +void dfl_feature_dev_use_end(struct dfl_feature_platform_data *pdata) +{ + clear_bit_unlock(DEV_STATUS_IN_USE, &pdata->dev_status); +} + +static inline +void dfl_fpga_pdata_set_private(struct dfl_feature_platform_data *pdata, + void *private) +{ + pdata->private = private; +} + +static inline +void *dfl_fpga_pdata_get_private(struct dfl_feature_platform_data *pdata) +{ + return pdata->private; +} + +struct dfl_feature_ops { + int (*init)(struct platform_device *pdev, struct dfl_feature *feature); + void (*uinit)(struct platform_device *pdev, + struct dfl_feature *feature); + long (*ioctl)(struct platform_device *pdev, struct dfl_feature *feature, + unsigned int cmd, unsigned long arg); +}; + +#define DFL_FPGA_FEATURE_DEV_FME "dfl-fme" +#define DFL_FPGA_FEATURE_DEV_PORT "dfl-port" + +static inline int dfl_feature_platform_data_size(const int num) +{ + return sizeof(struct dfl_feature_platform_data) + + num * sizeof(struct dfl_feature); +} + +void dfl_fpga_dev_feature_uinit(struct platform_device *pdev); +int dfl_fpga_dev_feature_init(struct platform_device *pdev, + struct dfl_feature_driver *feature_drvs); + +int dfl_fpga_dev_ops_register(struct platform_device *pdev, + const struct file_operations *fops, + struct module *owner); +void dfl_fpga_dev_ops_unregister(struct platform_device *pdev); + +static inline +struct platform_device *dfl_fpga_inode_to_feature_dev(struct inode *inode) +{ + struct dfl_feature_platform_data *pdata; + + pdata = container_of(inode->i_cdev, struct dfl_feature_platform_data, + cdev); + return pdata->dev; +} + +#define dfl_fpga_dev_for_each_feature(pdata, feature) \ + for ((feature) = (pdata)->features; \ + (feature) < (pdata)->features + (pdata)->num; (feature)++) + +static inline +struct dfl_feature *dfl_get_feature_by_id(struct device *dev, u64 id) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) + if (feature->id == id) + return feature; + + return NULL; +} + +static inline +void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u64 id) +{ + struct dfl_feature *feature = dfl_get_feature_by_id(dev, id); + + if (feature && feature->ioaddr) + return feature->ioaddr; + + WARN_ON(1); + return NULL; +} + +static inline bool is_dfl_feature_present(struct device *dev, u64 id) +{ + return !!dfl_get_feature_ioaddr_by_id(dev, id); +} + +static inline +struct device *dfl_fpga_pdata_to_parent(struct dfl_feature_platform_data *pdata) +{ + return pdata->dev->dev.parent->parent; +} + +static inline bool dfl_feature_is_fme(void __iomem *base) +{ + u64 v = readq(base + DFH); + + return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) && + (FIELD_GET(DFH_ID, v) == DFH_ID_FIU_FME); +} + +static inline bool dfl_feature_is_port(void __iomem *base) +{ + u64 v = readq(base + DFH); + + return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) && + (FIELD_GET(DFH_ID, v) == DFH_ID_FIU_PORT); +} + +/** + * struct dfl_fpga_enum_info - DFL FPGA enumeration information + * + * @dev: parent device. + * @dfls: list of device feature lists. + */ +struct dfl_fpga_enum_info { + struct device *dev; + struct list_head dfls; +}; + +/** + * struct dfl_fpga_enum_dfl - DFL FPGA enumeration device feature list info + * + * @start: base address of this device feature list. + * @len: size of this device feature list. + * @ioaddr: mapped base address of this device feature list. + * @node: node in list of device feature lists. + */ +struct dfl_fpga_enum_dfl { + resource_size_t start; + resource_size_t len; + + void __iomem *ioaddr; + + struct list_head node; +}; + +struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev); +int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, + resource_size_t start, resource_size_t len, + void __iomem *ioaddr); +void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info); + +/** + * struct dfl_fpga_cdev - container device of DFL based FPGA + * + * @parent: parent device of this container device. + * @region: base fpga region. + * @fme_dev: FME feature device under this container device. + * @lock: mutex lock to protect the port device list. + * @port_dev_list: list of all port feature devices under this container device. + */ +struct dfl_fpga_cdev { + struct device *parent; + struct fpga_region *region; + struct device *fme_dev; + struct mutex lock; + struct list_head port_dev_list; +}; + +struct dfl_fpga_cdev * +dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info); +void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev); + +/* + * need to drop the device reference with put_device() after use port platform + * device returned by __dfl_fpga_cdev_find_port and dfl_fpga_cdev_find_port + * functions. + */ +struct platform_device * +__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)); + +static inline struct platform_device * +dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)) +{ + struct platform_device *pdev; + + mutex_lock(&cdev->lock); + pdev = __dfl_fpga_cdev_find_port(cdev, data, match); + mutex_unlock(&cdev->lock); + + return pdev; +} +#endif /* __FPGA_DFL_H */ diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c new file mode 100644 index 000000000..c983dac97 --- /dev/null +++ b/drivers/fpga/fpga-bridge.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Bridge Framework Driver + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * Copyright (C) 2017 Intel Corporation + */ +#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*/ +static 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); + +static struct fpga_bridge *__fpga_bridge_get(struct device *dev, + struct fpga_image_info *info) +{ + struct fpga_bridge *bridge; + int ret = -ENODEV; + + bridge = to_fpga_bridge(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); +} + +/** + * 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; + + dev = class_find_device(fpga_bridge_class, NULL, np, + fpga_bridge_of_node_match); + if (!dev) + return ERR_PTR(-ENODEV); + + return __fpga_bridge_get(dev, info); +} +EXPORT_SYMBOL_GPL(of_fpga_bridge_get); + +static int fpga_bridge_dev_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +/** + * fpga_bridge_get - get an exclusive reference to a fpga bridge + * @dev: parent device that fpga bridge was registered with + * @info: fpga manager info + * + * Given a device, get an exclusive reference to a fpga bridge. + * + * Return: fpga bridge struct or IS_ERR() condition containing error code. + */ +struct fpga_bridge *fpga_bridge_get(struct device *dev, + struct fpga_image_info *info) +{ + struct device *bridge_dev; + + bridge_dev = class_find_device(fpga_bridge_class, NULL, dev, + fpga_bridge_dev_match); + if (!bridge_dev) + return ERR_PTR(-ENODEV); + + return __fpga_bridge_get(bridge_dev, info); +} +EXPORT_SYMBOL_GPL(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; + int ret; + + list_for_each_entry(bridge, bridge_list, 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; + int ret; + + list_for_each_entry(bridge, bridge_list, 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, *next; + unsigned long flags; + + list_for_each_entry_safe(bridge, next, bridge_list, 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); + +/** + * of_fpga_bridge_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 of_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(of_fpga_bridge_get_to_list); + +/** + * fpga_bridge_get_to_list - given device, get a bridge, add it to a list + * + * @dev: FPGA bridge device + * @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 fpga_bridge_get() othewise. + */ +int fpga_bridge_get_to_list(struct device *dev, + struct fpga_image_info *info, + struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + unsigned long flags; + + bridge = fpga_bridge_get(dev, 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_create - create and initialize a struct fpga_bridge + * @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: struct fpga_bridge or NULL + */ +struct fpga_bridge *fpga_bridge_create(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 NULL; + } + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return NULL; + + 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.groups = br_ops->groups; + bridge->dev.class = fpga_bridge_class; + bridge->dev.parent = dev; + bridge->dev.of_node = dev->of_node; + bridge->dev.id = id; + + ret = dev_set_name(&bridge->dev, "br%d", id); + if (ret) + goto error_device; + + return bridge; + +error_device: + ida_simple_remove(&fpga_bridge_ida, id); +error_kfree: + kfree(bridge); + + return NULL; +} +EXPORT_SYMBOL_GPL(fpga_bridge_create); + +/** + * fpga_bridge_free - free a fpga bridge and its id + * @bridge: FPGA bridge struct created by fpga_bridge_create + */ +void fpga_bridge_free(struct fpga_bridge *bridge) +{ + ida_simple_remove(&fpga_bridge_ida, bridge->dev.id); + kfree(bridge); +} +EXPORT_SYMBOL_GPL(fpga_bridge_free); + +/** + * fpga_bridge_register - register a fpga bridge + * @bridge: FPGA bridge struct created by fpga_bridge_create + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_register(struct fpga_bridge *bridge) +{ + struct device *dev = &bridge->dev; + int ret; + + ret = device_add(dev); + if (ret) + return ret; + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + dev_info(dev->parent, "fpga bridge [%s] registered\n", bridge->name); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_register); + +/** + * fpga_bridge_unregister - unregister and free a fpga bridge + * @bridge: FPGA bridge struct created by fpga_bridge_create + */ +void fpga_bridge_unregister(struct fpga_bridge *bridge) +{ + /* + * 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); + + fpga_bridge_free(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@kernel.org>"); +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 new file mode 100644 index 000000000..a41b07e37 --- /dev/null +++ b/drivers/fpga/fpga-mgr.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Manager Core + * + * Copyright (C) 2013-2015 Altera Corporation + * Copyright (C) 2017 Intel Corporation + * + * With code from the mailing list: + * Copyright (C) 2013 Xilinx, Inc. + */ +#include <linux/firmware.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/idr.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/scatterlist.h> +#include <linux/highmem.h> + +static DEFINE_IDA(fpga_mgr_ida); +static struct class *fpga_mgr_class; + +/** + * fpga_image_info_alloc - Allocate a FPGA image info struct + * @dev: owning device + * + * Return: struct fpga_image_info or NULL + */ +struct fpga_image_info *fpga_image_info_alloc(struct device *dev) +{ + struct fpga_image_info *info; + + get_device(dev); + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) { + put_device(dev); + return NULL; + } + + info->dev = dev; + + return info; +} +EXPORT_SYMBOL_GPL(fpga_image_info_alloc); + +/** + * fpga_image_info_free - Free a FPGA image info struct + * @info: FPGA image info struct to free + */ +void fpga_image_info_free(struct fpga_image_info *info) +{ + struct device *dev; + + if (!info) + return; + + dev = info->dev; + if (info->firmware_name) + devm_kfree(dev, info->firmware_name); + + devm_kfree(dev, info); + put_device(dev); +} +EXPORT_SYMBOL_GPL(fpga_image_info_free); + +/* + * 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. The low level driver only gets to see the first + * initial_header_size bytes in the buffer. + */ +static int fpga_mgr_write_init_buf(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + int ret; + + mgr->state = FPGA_MGR_STATE_WRITE_INIT; + if (!mgr->mops->initial_header_size) + ret = mgr->mops->write_init(mgr, info, NULL, 0); + else + ret = mgr->mops->write_init( + mgr, info, buf, min(mgr->mops->initial_header_size, count)); + + if (ret) { + dev_err(&mgr->dev, "Error preparing FPGA for writing\n"); + mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR; + return ret; + } + + return 0; +} + +static int fpga_mgr_write_init_sg(struct fpga_manager *mgr, + struct fpga_image_info *info, + struct sg_table *sgt) +{ + struct sg_mapping_iter miter; + size_t len; + char *buf; + int ret; + + if (!mgr->mops->initial_header_size) + return fpga_mgr_write_init_buf(mgr, info, NULL, 0); + + /* + * First try to use miter to map the first fragment to access the + * header, this is the typical path. + */ + sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG); + if (sg_miter_next(&miter) && + miter.length >= mgr->mops->initial_header_size) { + ret = fpga_mgr_write_init_buf(mgr, info, miter.addr, + miter.length); + sg_miter_stop(&miter); + return ret; + } + sg_miter_stop(&miter); + + /* Otherwise copy the fragments into temporary memory. */ + buf = kmalloc(mgr->mops->initial_header_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len = sg_copy_to_buffer(sgt->sgl, sgt->nents, buf, + mgr->mops->initial_header_size); + ret = fpga_mgr_write_init_buf(mgr, info, buf, len); + + kfree(buf); + + return ret; +} + +/* + * After all the FPGA image has been written, do the device specific steps to + * finish and set the FPGA into operating mode. + */ +static int fpga_mgr_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + int ret; + + mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE; + ret = mgr->mops->write_complete(mgr, info); + if (ret) { + dev_err(&mgr->dev, "Error after writing image data to FPGA\n"); + mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; + return ret; + } + mgr->state = FPGA_MGR_STATE_OPERATING; + + return 0; +} + +/** + * fpga_mgr_buf_load_sg - load fpga from image in buffer from a scatter list + * @mgr: fpga manager + * @info: fpga image specific information + * @sgt: scatterlist table + * + * 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() or fpga_mgr_get() and checked that it is + * not an error code. + * + * This is the preferred entry point for FPGA programming, it does not require + * any contiguous kernel memory. + * + * Return: 0 on success, negative error code otherwise. + */ +static int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, + struct fpga_image_info *info, + struct sg_table *sgt) +{ + int ret; + + ret = fpga_mgr_write_init_sg(mgr, info, sgt); + if (ret) + return ret; + + /* Write the FPGA image to the FPGA. */ + mgr->state = FPGA_MGR_STATE_WRITE; + if (mgr->mops->write_sg) { + ret = mgr->mops->write_sg(mgr, sgt); + } else { + struct sg_mapping_iter miter; + + sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG); + while (sg_miter_next(&miter)) { + ret = mgr->mops->write(mgr, miter.addr, miter.length); + if (ret) + break; + } + sg_miter_stop(&miter); + } + + if (ret) { + dev_err(&mgr->dev, "Error while writing image data to FPGA\n"); + mgr->state = FPGA_MGR_STATE_WRITE_ERR; + return ret; + } + + return fpga_mgr_write_complete(mgr, info); +} + +static int fpga_mgr_buf_load_mapped(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + int ret; + + ret = fpga_mgr_write_init_buf(mgr, info, buf, count); + if (ret) + return ret; + + /* + * Write the FPGA image to the FPGA. + */ + mgr->state = FPGA_MGR_STATE_WRITE; + ret = mgr->mops->write(mgr, buf, count); + if (ret) { + dev_err(&mgr->dev, "Error while writing image data to FPGA\n"); + mgr->state = FPGA_MGR_STATE_WRITE_ERR; + return ret; + } + + return fpga_mgr_write_complete(mgr, info); +} + +/** + * fpga_mgr_buf_load - load fpga from image in buffer + * @mgr: fpga manager + * @info: fpga image info + * @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. + * + * Return: 0 on success, negative error code otherwise. + */ +static int fpga_mgr_buf_load(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct page **pages; + struct sg_table sgt; + const void *p; + int nr_pages; + int index; + int rc; + + /* + * This is just a fast path if the caller has already created a + * contiguous kernel buffer and the driver doesn't require SG, non-SG + * drivers will still work on the slow path. + */ + if (mgr->mops->write) + return fpga_mgr_buf_load_mapped(mgr, info, buf, count); + + /* + * Convert the linear kernel pointer into a sg_table of pages for use + * by the driver. + */ + nr_pages = DIV_ROUND_UP((unsigned long)buf + count, PAGE_SIZE) - + (unsigned long)buf / PAGE_SIZE; + pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL); + if (!pages) + return -ENOMEM; + + p = buf - offset_in_page(buf); + for (index = 0; index < nr_pages; index++) { + if (is_vmalloc_addr(p)) + pages[index] = vmalloc_to_page(p); + else + pages[index] = kmap_to_page((void *)p); + if (!pages[index]) { + kfree(pages); + return -EFAULT; + } + p += PAGE_SIZE; + } + + /* + * The temporary pages list is used to code share the merging algorithm + * in sg_alloc_table_from_pages + */ + rc = sg_alloc_table_from_pages(&sgt, pages, index, offset_in_page(buf), + count, GFP_KERNEL); + kfree(pages); + if (rc) + return rc; + + rc = fpga_mgr_buf_load_sg(mgr, info, &sgt); + sg_free_table(&sgt); + + return rc; +} + +/** + * fpga_mgr_firmware_load - request firmware and load to fpga + * @mgr: fpga manager + * @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() or fpga_mgr_get() and checked that it is not an error + * code. + * + * Return: 0 on success, negative error code otherwise. + */ +static int fpga_mgr_firmware_load(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *image_name) +{ + struct device *dev = &mgr->dev; + const struct firmware *fw; + int ret; + + dev_info(dev, "writing %s to %s\n", image_name, mgr->name); + + mgr->state = FPGA_MGR_STATE_FIRMWARE_REQ; + + ret = request_firmware(&fw, image_name, dev); + if (ret) { + mgr->state = FPGA_MGR_STATE_FIRMWARE_REQ_ERR; + dev_err(dev, "Error requesting firmware %s\n", image_name); + return ret; + } + + ret = fpga_mgr_buf_load(mgr, info, fw->data, fw->size); + + release_firmware(fw); + + return ret; +} + +/** + * fpga_mgr_load - load FPGA from scatter/gather table, buffer, or firmware + * @mgr: fpga manager + * @info: fpga image information. + * + * Load the FPGA from an image which is indicated in @info. If successful, the + * FPGA ends up in operating mode. + * + * Return: 0 on success, negative error code otherwise. + */ +int fpga_mgr_load(struct fpga_manager *mgr, struct fpga_image_info *info) +{ + if (info->sgt) + return fpga_mgr_buf_load_sg(mgr, info, info->sgt); + if (info->buf && info->count) + return fpga_mgr_buf_load(mgr, info, info->buf, info->count); + if (info->firmware_name) + return fpga_mgr_firmware_load(mgr, info, info->firmware_name); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(fpga_mgr_load); + +static const char * const state_str[] = { + [FPGA_MGR_STATE_UNKNOWN] = "unknown", + [FPGA_MGR_STATE_POWER_OFF] = "power off", + [FPGA_MGR_STATE_POWER_UP] = "power up", + [FPGA_MGR_STATE_RESET] = "reset", + + /* requesting FPGA image from firmware */ + [FPGA_MGR_STATE_FIRMWARE_REQ] = "firmware request", + [FPGA_MGR_STATE_FIRMWARE_REQ_ERR] = "firmware request error", + + /* Preparing FPGA to receive image */ + [FPGA_MGR_STATE_WRITE_INIT] = "write init", + [FPGA_MGR_STATE_WRITE_INIT_ERR] = "write init error", + + /* Writing image to FPGA */ + [FPGA_MGR_STATE_WRITE] = "write", + [FPGA_MGR_STATE_WRITE_ERR] = "write error", + + /* Finishing configuration after image has been written */ + [FPGA_MGR_STATE_WRITE_COMPLETE] = "write complete", + [FPGA_MGR_STATE_WRITE_COMPLETE_ERR] = "write complete error", + + /* FPGA reports to be in normal operating mode */ + [FPGA_MGR_STATE_OPERATING] = "operating", +}; + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + + return sprintf(buf, "%s\n", mgr->name); +} + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + + return sprintf(buf, "%s\n", state_str[mgr->state]); +} + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + u64 status; + int len = 0; + + if (!mgr->mops->status) + return -ENOENT; + + status = mgr->mops->status(mgr); + + if (status & FPGA_MGR_STATUS_OPERATION_ERR) + len += sprintf(buf + len, "reconfig operation error\n"); + if (status & FPGA_MGR_STATUS_CRC_ERR) + len += sprintf(buf + len, "reconfig CRC error\n"); + if (status & FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR) + len += sprintf(buf + len, "reconfig incompatible image\n"); + if (status & FPGA_MGR_STATUS_IP_PROTOCOL_ERR) + len += sprintf(buf + len, "reconfig IP protocol error\n"); + if (status & FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR) + len += sprintf(buf + len, "reconfig fifo overflow error\n"); + + return len; +} + +static DEVICE_ATTR_RO(name); +static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RO(status); + +static struct attribute *fpga_mgr_attrs[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + &dev_attr_status.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_mgr); + +static struct fpga_manager *__fpga_mgr_get(struct device *dev) +{ + struct fpga_manager *mgr; + + mgr = to_fpga_manager(dev); + + if (!try_module_get(dev->parent->driver->owner)) + goto err_dev; + + return mgr; + +err_dev: + put_device(dev); + return ERR_PTR(-ENODEV); +} + +static int fpga_mgr_dev_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +/** + * fpga_mgr_get - Given a device, get a reference to a fpga mgr. + * @dev: parent device that fpga mgr was registered with + * + * 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 - Given a device node, get a reference to a fpga mgr. + * + * @node: device node + * + * 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); + +/** + * fpga_mgr_put - release a reference to a fpga manager + * @mgr: fpga manager structure + */ +void fpga_mgr_put(struct fpga_manager *mgr) +{ + module_put(mgr->dev.parent->driver->owner); + put_device(&mgr->dev); +} +EXPORT_SYMBOL_GPL(fpga_mgr_put); + +/** + * fpga_mgr_lock - Lock FPGA manager for exclusive use + * @mgr: fpga manager + * + * Given a pointer to FPGA Manager (from fpga_mgr_get() or + * of_fpga_mgr_put()) attempt to get the mutex. The user should call + * fpga_mgr_lock() and verify that it returns 0 before attempting to + * program the FPGA. Likewise, the user should call fpga_mgr_unlock + * when done programming the FPGA. + * + * Return: 0 for success or -EBUSY + */ +int fpga_mgr_lock(struct fpga_manager *mgr) +{ + if (!mutex_trylock(&mgr->ref_mutex)) { + dev_err(&mgr->dev, "FPGA manager is in use.\n"); + return -EBUSY; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_mgr_lock); + +/** + * fpga_mgr_unlock - Unlock FPGA manager after done programming + * @mgr: fpga manager + */ +void fpga_mgr_unlock(struct fpga_manager *mgr) +{ + mutex_unlock(&mgr->ref_mutex); +} +EXPORT_SYMBOL_GPL(fpga_mgr_unlock); + +/** + * fpga_mgr_create - create and initialize a FPGA manager struct + * @dev: fpga manager device from pdev + * @name: fpga manager name + * @mops: pointer to structure of fpga manager ops + * @priv: fpga manager private data + * + * Return: pointer to struct fpga_manager or NULL + */ +struct fpga_manager *fpga_mgr_create(struct device *dev, const char *name, + const struct fpga_manager_ops *mops, + void *priv) +{ + struct fpga_manager *mgr; + int id, ret; + + if (!mops || !mops->write_complete || !mops->state || + !mops->write_init || (!mops->write && !mops->write_sg) || + (mops->write && mops->write_sg)) { + dev_err(dev, "Attempt to register without fpga_manager_ops\n"); + return NULL; + } + + if (!name || !strlen(name)) { + dev_err(dev, "Attempt to register with no name!\n"); + return NULL; + } + + mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); + if (!mgr) + return NULL; + + id = ida_simple_get(&fpga_mgr_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto error_kfree; + } + + mutex_init(&mgr->ref_mutex); + + mgr->name = name; + mgr->mops = mops; + mgr->priv = priv; + + device_initialize(&mgr->dev); + mgr->dev.class = fpga_mgr_class; + mgr->dev.groups = mops->groups; + mgr->dev.parent = dev; + mgr->dev.of_node = dev->of_node; + mgr->dev.id = id; + + ret = dev_set_name(&mgr->dev, "fpga%d", id); + if (ret) + goto error_device; + + return mgr; + +error_device: + ida_simple_remove(&fpga_mgr_ida, id); +error_kfree: + kfree(mgr); + + return NULL; +} +EXPORT_SYMBOL_GPL(fpga_mgr_create); + +/** + * fpga_mgr_free - deallocate a FPGA manager + * @mgr: fpga manager struct created by fpga_mgr_create + */ +void fpga_mgr_free(struct fpga_manager *mgr) +{ + ida_simple_remove(&fpga_mgr_ida, mgr->dev.id); + kfree(mgr); +} +EXPORT_SYMBOL_GPL(fpga_mgr_free); + +/** + * fpga_mgr_register - register a FPGA manager + * @mgr: fpga manager struct created by fpga_mgr_create + * + * Return: 0 on success, negative error code otherwise. + */ +int fpga_mgr_register(struct fpga_manager *mgr) +{ + int ret; + + /* + * Initialize framework state by requesting low level driver read state + * from device. FPGA may be in reset mode or may have been programmed + * by bootloader or EEPROM. + */ + mgr->state = mgr->mops->state(mgr); + + ret = device_add(&mgr->dev); + if (ret) + goto error_device; + + dev_info(&mgr->dev, "%s registered\n", mgr->name); + + return 0; + +error_device: + ida_simple_remove(&fpga_mgr_ida, mgr->dev.id); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_mgr_register); + +/** + * fpga_mgr_unregister - unregister and free a FPGA manager + * @mgr: fpga manager struct + */ +void fpga_mgr_unregister(struct fpga_manager *mgr) +{ + dev_info(&mgr->dev, "%s %s\n", __func__, mgr->name); + + /* + * If the low level driver provides a method for putting fpga into + * a desired state upon unregister, do it. + */ + if (mgr->mops->fpga_remove) + mgr->mops->fpga_remove(mgr); + + device_unregister(&mgr->dev); +} +EXPORT_SYMBOL_GPL(fpga_mgr_unregister); + +static void fpga_mgr_dev_release(struct device *dev) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + + fpga_mgr_free(mgr); +} + +static int __init fpga_mgr_class_init(void) +{ + pr_info("FPGA manager framework\n"); + + fpga_mgr_class = class_create(THIS_MODULE, "fpga_manager"); + if (IS_ERR(fpga_mgr_class)) + return PTR_ERR(fpga_mgr_class); + + fpga_mgr_class->dev_groups = fpga_mgr_groups; + fpga_mgr_class->dev_release = fpga_mgr_dev_release; + + return 0; +} + +static void __exit fpga_mgr_class_exit(void) +{ + class_destroy(fpga_mgr_class); + ida_destroy(&fpga_mgr_ida); +} + +MODULE_AUTHOR("Alan Tull <atull@kernel.org>"); +MODULE_DESCRIPTION("FPGA manager framework"); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_mgr_class_init); +module_exit(fpga_mgr_class_exit); diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c new file mode 100644 index 000000000..0d65220d5 --- /dev/null +++ b/drivers/fpga/fpga-region.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Region - Device Tree support for FPGA programming under Linux + * + * Copyright (C) 2013-2016 Altera Corporation + * Copyright (C) 2017 Intel Corporation + */ +#include <linux/fpga/fpga-bridge.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/fpga/fpga-region.h> +#include <linux/idr.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +static DEFINE_IDA(fpga_region_ida); +static struct class *fpga_region_class; + +struct fpga_region *fpga_region_class_find( + struct device *start, const void *data, + int (*match)(struct device *, const void *)) +{ + struct device *dev; + + dev = class_find_device(fpga_region_class, start, data, match); + if (!dev) + return NULL; + + return to_fpga_region(dev); +} +EXPORT_SYMBOL_GPL(fpga_region_class_find); + +/** + * 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); + if (!try_module_get(dev->parent->driver->owner)) { + put_device(dev); + mutex_unlock(®ion->mutex); + return ERR_PTR(-ENODEV); + } + + dev_dbg(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(dev, "put\n"); + + module_put(dev->parent->driver->owner); + put_device(dev); + mutex_unlock(®ion->mutex); +} + +/** + * fpga_region_program_fpga - program FPGA + * + * @region: FPGA region + * + * Program an FPGA using fpga image info (region->info). + * If the region has a get_bridges function, the exclusive reference for the + * bridges will be held if programming succeeds. This is intended to prevent + * reprogramming the region until the caller considers it safe to do so. + * The caller will need to call fpga_bridges_put() before attempting to + * reprogram the region. + * + * Return 0 for success or negative error code. + */ +int fpga_region_program_fpga(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + struct fpga_image_info *info = region->info; + int ret; + + region = fpga_region_get(region); + if (IS_ERR(region)) { + dev_err(dev, "failed to get FPGA region\n"); + return PTR_ERR(region); + } + + ret = fpga_mgr_lock(region->mgr); + if (ret) { + dev_err(dev, "FPGA manager is busy\n"); + goto err_put_region; + } + + /* + * In some cases, we already have a list of bridges in the + * fpga region struct. Or we don't have any bridges. + */ + if (region->get_bridges) { + ret = region->get_bridges(region); + if (ret) { + dev_err(dev, "failed to get fpga region bridges\n"); + goto err_unlock_mgr; + } + } + + ret = fpga_bridges_disable(®ion->bridge_list); + if (ret) { + dev_err(dev, "failed to disable bridges\n"); + goto err_put_br; + } + + ret = fpga_mgr_load(region->mgr, info); + if (ret) { + dev_err(dev, "failed to load FPGA image\n"); + goto err_put_br; + } + + ret = fpga_bridges_enable(®ion->bridge_list); + if (ret) { + dev_err(dev, "failed to enable region bridges\n"); + goto err_put_br; + } + + fpga_mgr_unlock(region->mgr); + fpga_region_put(region); + + return 0; + +err_put_br: + if (region->get_bridges) + fpga_bridges_put(®ion->bridge_list); +err_unlock_mgr: + fpga_mgr_unlock(region->mgr); +err_put_region: + fpga_region_put(region); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_region_program_fpga); + +static ssize_t compat_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_region *region = to_fpga_region(dev); + + if (!region->compat_id) + return -ENOENT; + + return sprintf(buf, "%016llx%016llx\n", + (unsigned long long)region->compat_id->id_h, + (unsigned long long)region->compat_id->id_l); +} + +static DEVICE_ATTR_RO(compat_id); + +static struct attribute *fpga_region_attrs[] = { + &dev_attr_compat_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_region); + +/** + * fpga_region_create - alloc and init a struct fpga_region + * @dev: device parent + * @mgr: manager that programs this region + * @get_bridges: optional function to get bridges to a list + * + * Return: struct fpga_region or NULL + */ +struct fpga_region +*fpga_region_create(struct device *dev, + struct fpga_manager *mgr, + int (*get_bridges)(struct fpga_region *)) +{ + struct fpga_region *region; + int id, ret = 0; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return NULL; + + id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL); + if (id < 0) + goto err_free; + + region->mgr = mgr; + region->get_bridges = get_bridges; + 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 = dev->of_node; + region->dev.id = id; + + ret = dev_set_name(®ion->dev, "region%d", id); + if (ret) + goto err_remove; + + return region; + +err_remove: + ida_simple_remove(&fpga_region_ida, id); +err_free: + kfree(region); + + return NULL; +} +EXPORT_SYMBOL_GPL(fpga_region_create); + +/** + * fpga_region_free - free a struct fpga_region + * @region: FPGA region created by fpga_region_create + */ +void fpga_region_free(struct fpga_region *region) +{ + ida_simple_remove(&fpga_region_ida, region->dev.id); + kfree(region); +} +EXPORT_SYMBOL_GPL(fpga_region_free); + +/** + * fpga_region_register - register a FPGA region + * @region: FPGA region created by fpga_region_create + * Return: 0 or -errno + */ +int fpga_region_register(struct fpga_region *region) +{ + return device_add(®ion->dev); + +} +EXPORT_SYMBOL_GPL(fpga_region_register); + +/** + * fpga_region_unregister - unregister and free a FPGA region + * @region: FPGA region + */ +void fpga_region_unregister(struct fpga_region *region) +{ + device_unregister(®ion->dev); +} +EXPORT_SYMBOL_GPL(fpga_region_unregister); + +static void fpga_region_dev_release(struct device *dev) +{ + struct fpga_region *region = to_fpga_region(dev); + + fpga_region_free(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) +{ + 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_groups = fpga_region_groups; + fpga_region_class->dev_release = fpga_region_dev_release; + + return 0; +} + +static void __exit fpga_region_exit(void) +{ + 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@kernel.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/ice40-spi.c b/drivers/fpga/ice40-spi.c new file mode 100644 index 000000000..5981c7ee7 --- /dev/null +++ b/drivers/fpga/ice40-spi.c @@ -0,0 +1,220 @@ +/* + * FPGA Manager Driver for Lattice iCE40. + * + * Copyright (c) 2016 Joel Holdsworth + * + * 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 driver adds support to the FPGA manager for configuring the SRAM of + * Lattice iCE40 FPGAs through slave SPI. + */ + +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/spi/spi.h> +#include <linux/stringify.h> + +#define ICE40_SPI_MAX_SPEED 25000000 /* Hz */ +#define ICE40_SPI_MIN_SPEED 1000000 /* Hz */ + +#define ICE40_SPI_RESET_DELAY 1 /* us (>200ns) */ +#define ICE40_SPI_HOUSEKEEPING_DELAY 1200 /* us */ + +#define ICE40_SPI_NUM_ACTIVATION_BYTES DIV_ROUND_UP(49, 8) + +struct ice40_fpga_priv { + struct spi_device *dev; + struct gpio_desc *reset; + struct gpio_desc *cdone; +}; + +static enum fpga_mgr_states ice40_fpga_ops_state(struct fpga_manager *mgr) +{ + struct ice40_fpga_priv *priv = mgr->priv; + + return gpiod_get_value(priv->cdone) ? FPGA_MGR_STATE_OPERATING : + FPGA_MGR_STATE_UNKNOWN; +} + +static int ice40_fpga_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct ice40_fpga_priv *priv = mgr->priv; + struct spi_device *dev = priv->dev; + struct spi_message message; + struct spi_transfer assert_cs_then_reset_delay = { + .cs_change = 1, + .delay_usecs = ICE40_SPI_RESET_DELAY + }; + struct spi_transfer housekeeping_delay_then_release_cs = { + .delay_usecs = ICE40_SPI_HOUSEKEEPING_DELAY + }; + int ret; + + if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&dev->dev, + "Partial reconfiguration is not supported\n"); + return -ENOTSUPP; + } + + /* Lock the bus, assert CRESET_B and SS_B and delay >200ns */ + spi_bus_lock(dev->master); + + gpiod_set_value(priv->reset, 1); + + spi_message_init(&message); + spi_message_add_tail(&assert_cs_then_reset_delay, &message); + ret = spi_sync_locked(dev, &message); + + /* Come out of reset */ + gpiod_set_value(priv->reset, 0); + + /* Abort if the chip-select failed */ + if (ret) + goto fail; + + /* Check CDONE is de-asserted i.e. the FPGA is reset */ + if (gpiod_get_value(priv->cdone)) { + dev_err(&dev->dev, "Device reset failed, CDONE is asserted\n"); + ret = -EIO; + goto fail; + } + + /* Wait for the housekeeping to complete, and release SS_B */ + spi_message_init(&message); + spi_message_add_tail(&housekeeping_delay_then_release_cs, &message); + ret = spi_sync_locked(dev, &message); + +fail: + spi_bus_unlock(dev->master); + + return ret; +} + +static int ice40_fpga_ops_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct ice40_fpga_priv *priv = mgr->priv; + + return spi_write(priv->dev, buf, count); +} + +static int ice40_fpga_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct ice40_fpga_priv *priv = mgr->priv; + struct spi_device *dev = priv->dev; + const u8 padding[ICE40_SPI_NUM_ACTIVATION_BYTES] = {0}; + + /* Check CDONE is asserted */ + if (!gpiod_get_value(priv->cdone)) { + dev_err(&dev->dev, + "CDONE was not asserted after firmware transfer\n"); + return -EIO; + } + + /* Send of zero-padding to activate the firmware */ + return spi_write(dev, padding, sizeof(padding)); +} + +static const struct fpga_manager_ops ice40_fpga_ops = { + .state = ice40_fpga_ops_state, + .write_init = ice40_fpga_ops_write_init, + .write = ice40_fpga_ops_write, + .write_complete = ice40_fpga_ops_write_complete, +}; + +static int ice40_fpga_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct ice40_fpga_priv *priv; + struct fpga_manager *mgr; + int ret; + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = spi; + + /* Check board setup data. */ + if (spi->max_speed_hz > ICE40_SPI_MAX_SPEED) { + dev_err(dev, "SPI speed is too high, maximum speed is " + __stringify(ICE40_SPI_MAX_SPEED) "\n"); + return -EINVAL; + } + + if (spi->max_speed_hz < ICE40_SPI_MIN_SPEED) { + dev_err(dev, "SPI speed is too low, minimum speed is " + __stringify(ICE40_SPI_MIN_SPEED) "\n"); + return -EINVAL; + } + + if (spi->mode & SPI_CPHA) { + dev_err(dev, "Bad SPI mode, CPHA not supported\n"); + return -EINVAL; + } + + /* Set up the GPIOs */ + priv->cdone = devm_gpiod_get(dev, "cdone", GPIOD_IN); + if (IS_ERR(priv->cdone)) { + ret = PTR_ERR(priv->cdone); + dev_err(dev, "Failed to get CDONE GPIO: %d\n", ret); + return ret; + } + + priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset)) { + ret = PTR_ERR(priv->reset); + dev_err(dev, "Failed to get CRESET_B GPIO: %d\n", ret); + return ret; + } + + mgr = fpga_mgr_create(dev, "Lattice iCE40 FPGA Manager", + &ice40_fpga_ops, priv); + if (!mgr) + return -ENOMEM; + + spi_set_drvdata(spi, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int ice40_fpga_remove(struct spi_device *spi) +{ + struct fpga_manager *mgr = spi_get_drvdata(spi); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static const struct of_device_id ice40_fpga_of_match[] = { + { .compatible = "lattice,ice40-fpga-mgr", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ice40_fpga_of_match); + +static struct spi_driver ice40_fpga_driver = { + .probe = ice40_fpga_probe, + .remove = ice40_fpga_remove, + .driver = { + .name = "ice40spi", + .of_match_table = of_match_ptr(ice40_fpga_of_match), + }, +}; + +module_spi_driver(ice40_fpga_driver); + +MODULE_AUTHOR("Joel Holdsworth <joel@airwebreathe.org.uk>"); +MODULE_DESCRIPTION("Lattice iCE40 FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/machxo2-spi.c b/drivers/fpga/machxo2-spi.c new file mode 100644 index 000000000..fa76239f9 --- /dev/null +++ b/drivers/fpga/machxo2-spi.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Lattice MachXO2 Slave SPI Driver + * + * Manage Lattice FPGA firmware that is loaded over SPI using + * the slave serial configuration interface. + * + * Copyright (C) 2018 Paolo Pisati <p.pisati@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/spi/spi.h> + +/* MachXO2 Programming Guide - sysCONFIG Programming Commands */ +#define IDCODE_PUB {0xe0, 0x00, 0x00, 0x00} +#define ISC_ENABLE {0xc6, 0x08, 0x00, 0x00} +#define ISC_ERASE {0x0e, 0x04, 0x00, 0x00} +#define ISC_PROGRAMDONE {0x5e, 0x00, 0x00, 0x00} +#define LSC_INITADDRESS {0x46, 0x00, 0x00, 0x00} +#define LSC_PROGINCRNV {0x70, 0x00, 0x00, 0x01} +#define LSC_READ_STATUS {0x3c, 0x00, 0x00, 0x00} +#define LSC_REFRESH {0x79, 0x00, 0x00, 0x00} + +/* + * Max CCLK in Slave SPI mode according to 'MachXO2 Family Data + * Sheet' sysCONFIG Port Timing Specifications (3-36) + */ +#define MACHXO2_MAX_SPEED 66000000 + +#define MACHXO2_LOW_DELAY_USEC 5 +#define MACHXO2_HIGH_DELAY_USEC 200 +#define MACHXO2_REFRESH_USEC 4800 +#define MACHXO2_MAX_BUSY_LOOP 128 +#define MACHXO2_MAX_REFRESH_LOOP 16 + +#define MACHXO2_PAGE_SIZE 16 +#define MACHXO2_BUF_SIZE (MACHXO2_PAGE_SIZE + 4) + +/* Status register bits, errors and error mask */ +#define BUSY 12 +#define DONE 8 +#define DVER 27 +#define ENAB 9 +#define ERRBITS 23 +#define ERRMASK 7 +#define FAIL 13 + +#define ENOERR 0 /* no error */ +#define EID 1 +#define ECMD 2 +#define ECRC 3 +#define EPREAM 4 /* preamble error */ +#define EABRT 5 /* abort error */ +#define EOVERFL 6 /* overflow error */ +#define ESDMEOF 7 /* SDM EOF */ + +static inline u8 get_err(unsigned long *status) +{ + return (*status >> ERRBITS) & ERRMASK; +} + +static int get_status(struct spi_device *spi, unsigned long *status) +{ + struct spi_message msg; + struct spi_transfer rx, tx; + static const u8 cmd[] = LSC_READ_STATUS; + int ret; + + memset(&rx, 0, sizeof(rx)); + memset(&tx, 0, sizeof(tx)); + tx.tx_buf = cmd; + tx.len = sizeof(cmd); + rx.rx_buf = status; + rx.len = 4; + spi_message_init(&msg); + spi_message_add_tail(&tx, &msg); + spi_message_add_tail(&rx, &msg); + ret = spi_sync(spi, &msg); + if (ret) + return ret; + + *status = be32_to_cpu(*status); + + return 0; +} + +#ifdef DEBUG +static const char *get_err_string(u8 err) +{ + switch (err) { + case ENOERR: return "No Error"; + case EID: return "ID ERR"; + case ECMD: return "CMD ERR"; + case ECRC: return "CRC ERR"; + case EPREAM: return "Preamble ERR"; + case EABRT: return "Abort ERR"; + case EOVERFL: return "Overflow ERR"; + case ESDMEOF: return "SDM EOF"; + } + + return "Default switch case"; +} +#endif + +static void dump_status_reg(unsigned long *status) +{ +#ifdef DEBUG + pr_debug("machxo2 status: 0x%08lX - done=%d, cfgena=%d, busy=%d, fail=%d, devver=%d, err=%s\n", + *status, test_bit(DONE, status), test_bit(ENAB, status), + test_bit(BUSY, status), test_bit(FAIL, status), + test_bit(DVER, status), get_err_string(get_err(status))); +#endif +} + +static int wait_until_not_busy(struct spi_device *spi) +{ + unsigned long status; + int ret, loop = 0; + + do { + ret = get_status(spi, &status); + if (ret) + return ret; + if (++loop >= MACHXO2_MAX_BUSY_LOOP) + return -EBUSY; + } while (test_bit(BUSY, &status)); + + return 0; +} + +static int machxo2_cleanup(struct fpga_manager *mgr) +{ + struct spi_device *spi = mgr->priv; + struct spi_message msg; + struct spi_transfer tx[2]; + static const u8 erase[] = ISC_ERASE; + static const u8 refresh[] = LSC_REFRESH; + int ret; + + memset(tx, 0, sizeof(tx)); + spi_message_init(&msg); + tx[0].tx_buf = &erase; + tx[0].len = sizeof(erase); + spi_message_add_tail(&tx[0], &msg); + ret = spi_sync(spi, &msg); + if (ret) + goto fail; + + ret = wait_until_not_busy(spi); + if (ret) + goto fail; + + spi_message_init(&msg); + tx[1].tx_buf = &refresh; + tx[1].len = sizeof(refresh); + tx[1].delay_usecs = MACHXO2_REFRESH_USEC; + spi_message_add_tail(&tx[1], &msg); + ret = spi_sync(spi, &msg); + if (ret) + goto fail; + + return 0; +fail: + dev_err(&mgr->dev, "Cleanup failed\n"); + + return ret; +} + +static enum fpga_mgr_states machxo2_spi_state(struct fpga_manager *mgr) +{ + struct spi_device *spi = mgr->priv; + unsigned long status; + + get_status(spi, &status); + if (!test_bit(BUSY, &status) && test_bit(DONE, &status) && + get_err(&status) == ENOERR) + return FPGA_MGR_STATE_OPERATING; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static int machxo2_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct spi_device *spi = mgr->priv; + struct spi_message msg; + struct spi_transfer tx[3]; + static const u8 enable[] = ISC_ENABLE; + static const u8 erase[] = ISC_ERASE; + static const u8 initaddr[] = LSC_INITADDRESS; + unsigned long status; + int ret; + + if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(&mgr->dev, + "Partial reconfiguration is not supported\n"); + return -ENOTSUPP; + } + + get_status(spi, &status); + dump_status_reg(&status); + memset(tx, 0, sizeof(tx)); + spi_message_init(&msg); + tx[0].tx_buf = &enable; + tx[0].len = sizeof(enable); + tx[0].delay_usecs = MACHXO2_LOW_DELAY_USEC; + spi_message_add_tail(&tx[0], &msg); + + tx[1].tx_buf = &erase; + tx[1].len = sizeof(erase); + spi_message_add_tail(&tx[1], &msg); + ret = spi_sync(spi, &msg); + if (ret) + goto fail; + + ret = wait_until_not_busy(spi); + if (ret) + goto fail; + + get_status(spi, &status); + if (test_bit(FAIL, &status)) { + ret = -EINVAL; + goto fail; + } + dump_status_reg(&status); + + spi_message_init(&msg); + tx[2].tx_buf = &initaddr; + tx[2].len = sizeof(initaddr); + spi_message_add_tail(&tx[2], &msg); + ret = spi_sync(spi, &msg); + if (ret) + goto fail; + + get_status(spi, &status); + dump_status_reg(&status); + + return 0; +fail: + dev_err(&mgr->dev, "Error during FPGA init.\n"); + + return ret; +} + +static int machxo2_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct spi_device *spi = mgr->priv; + struct spi_message msg; + struct spi_transfer tx; + static const u8 progincr[] = LSC_PROGINCRNV; + u8 payload[MACHXO2_BUF_SIZE]; + unsigned long status; + int i, ret; + + if (count % MACHXO2_PAGE_SIZE != 0) { + dev_err(&mgr->dev, "Malformed payload.\n"); + return -EINVAL; + } + get_status(spi, &status); + dump_status_reg(&status); + memcpy(payload, &progincr, sizeof(progincr)); + for (i = 0; i < count; i += MACHXO2_PAGE_SIZE) { + memcpy(&payload[sizeof(progincr)], &buf[i], MACHXO2_PAGE_SIZE); + memset(&tx, 0, sizeof(tx)); + spi_message_init(&msg); + tx.tx_buf = payload; + tx.len = MACHXO2_BUF_SIZE; + tx.delay_usecs = MACHXO2_HIGH_DELAY_USEC; + spi_message_add_tail(&tx, &msg); + ret = spi_sync(spi, &msg); + if (ret) { + dev_err(&mgr->dev, "Error loading the bitstream.\n"); + return ret; + } + } + get_status(spi, &status); + dump_status_reg(&status); + + return 0; +} + +static int machxo2_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct spi_device *spi = mgr->priv; + struct spi_message msg; + struct spi_transfer tx[2]; + static const u8 progdone[] = ISC_PROGRAMDONE; + static const u8 refresh[] = LSC_REFRESH; + unsigned long status; + int ret, refreshloop = 0; + + memset(tx, 0, sizeof(tx)); + spi_message_init(&msg); + tx[0].tx_buf = &progdone; + tx[0].len = sizeof(progdone); + spi_message_add_tail(&tx[0], &msg); + ret = spi_sync(spi, &msg); + if (ret) + goto fail; + ret = wait_until_not_busy(spi); + if (ret) + goto fail; + + get_status(spi, &status); + dump_status_reg(&status); + if (!test_bit(DONE, &status)) { + machxo2_cleanup(mgr); + ret = -EINVAL; + goto fail; + } + + do { + spi_message_init(&msg); + tx[1].tx_buf = &refresh; + tx[1].len = sizeof(refresh); + tx[1].delay_usecs = MACHXO2_REFRESH_USEC; + spi_message_add_tail(&tx[1], &msg); + ret = spi_sync(spi, &msg); + if (ret) + goto fail; + + /* check refresh status */ + get_status(spi, &status); + dump_status_reg(&status); + if (!test_bit(BUSY, &status) && test_bit(DONE, &status) && + get_err(&status) == ENOERR) + break; + if (++refreshloop == MACHXO2_MAX_REFRESH_LOOP) { + machxo2_cleanup(mgr); + ret = -EINVAL; + goto fail; + } + } while (1); + + get_status(spi, &status); + dump_status_reg(&status); + + return 0; +fail: + dev_err(&mgr->dev, "Refresh failed.\n"); + + return ret; +} + +static const struct fpga_manager_ops machxo2_ops = { + .state = machxo2_spi_state, + .write_init = machxo2_write_init, + .write = machxo2_write, + .write_complete = machxo2_write_complete, +}; + +static int machxo2_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct fpga_manager *mgr; + int ret; + + if (spi->max_speed_hz > MACHXO2_MAX_SPEED) { + dev_err(dev, "Speed is too high\n"); + return -EINVAL; + } + + mgr = fpga_mgr_create(dev, "Lattice MachXO2 SPI FPGA Manager", + &machxo2_ops, spi); + if (!mgr) + return -ENOMEM; + + spi_set_drvdata(spi, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int machxo2_spi_remove(struct spi_device *spi) +{ + struct fpga_manager *mgr = spi_get_drvdata(spi); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static const struct of_device_id of_match[] = { + { .compatible = "lattice,machxo2-slave-spi", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_match); + +static const struct spi_device_id lattice_ids[] = { + { "machxo2-slave-spi", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, lattice_ids); + +static struct spi_driver machxo2_spi_driver = { + .driver = { + .name = "machxo2-slave-spi", + .of_match_table = of_match_ptr(of_match), + }, + .probe = machxo2_spi_probe, + .remove = machxo2_spi_remove, + .id_table = lattice_ids, +}; + +module_spi_driver(machxo2_spi_driver) + +MODULE_AUTHOR("Paolo Pisati <p.pisati@gmail.com>"); +MODULE_DESCRIPTION("Load Lattice FPGA firmware over SPI"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/of-fpga-region.c b/drivers/fpga/of-fpga-region.c new file mode 100644 index 000000000..052a1342a --- /dev/null +++ b/drivers/fpga/of-fpga-region.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Region - Device Tree support for FPGA programming under Linux + * + * Copyright (C) 2013-2016 Altera Corporation + * Copyright (C) 2017 Intel Corporation + */ +#include <linux/fpga/fpga-bridge.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/fpga/fpga-region.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> + +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; +} + +/** + * of_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 *of_fpga_region_find(struct device_node *np) +{ + return fpga_region_class_find(NULL, np, fpga_region_of_node_match); +} + +/** + * of_fpga_region_get_mgr - get reference for FPGA manager + * @np: device node of 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 *of_fpga_region_get_mgr(struct device_node *np) +{ + 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(mgr_node); + of_node_put(np); + return mgr; + } + } + np = of_get_next_parent(np); + } + of_node_put(np); + + return ERR_PTR(-EINVAL); +} + +/** + * of_fpga_region_get_bridges - create a list of bridges + * @region: FPGA region + * + * 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 of_fpga_region_get_bridges(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + struct device_node *region_np = dev->of_node; + struct fpga_image_info *info = region->info; + struct device_node *br, *np, *parent_br = NULL; + int i, ret; + + /* If parent is a bridge, add to list */ + ret = of_fpga_bridge_get_to_list(region_np->parent, info, + ®ion->bridge_list); + + /* -EBUSY means parent is a bridge that is under use. Give up. */ + if (ret == -EBUSY) + return ret; + + /* Zero return code means parent was a bridge and was added to list. */ + if (!ret) + parent_br = region_np->parent; + + /* If overlay has a list of bridges, use it. */ + br = of_parse_phandle(info->overlay, "fpga-bridges", 0); + if (br) { + of_node_put(br); + np = info->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) { + of_node_put(br); + continue; + } + + /* If node is a bridge, get it and add to list */ + ret = of_fpga_bridge_get_to_list(br, info, + ®ion->bridge_list); + of_node_put(br); + + /* If any of the bridges are in use, give up */ + if (ret == -EBUSY) { + fpga_bridges_put(®ion->bridge_list); + return -EBUSY; + } + } + + return 0; +} + +/** + * 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: %pOF", + child_region); + + return ret; +} + +/** + * of_fpga_region_parse_ov - parse and check overlay applied to region + * + * @region: FPGA region + * @overlay: overlay applied to the FPGA region + * + * Given an overlay applied to a FPGA region, parse the FPGA image specific + * info in the overlay and do some checking. + * + * Returns: + * NULL if overlay doesn't direct us to program the FPGA. + * fpga_image_info struct if there is an image to program. + * error code for invalid overlay. + */ +static struct fpga_image_info *of_fpga_region_parse_ov( + struct fpga_region *region, + struct device_node *overlay) +{ + struct device *dev = ®ion->dev; + struct fpga_image_info *info; + const char *firmware_name; + int ret; + + if (region->info) { + dev_err(dev, "Region already has overlay applied.\n"); + return ERR_PTR(-EINVAL); + } + + /* + * Reject overlay if child FPGA Regions added in the overlay have + * firmware-name property (would mean that an FPGA region that has + * not been added to the live tree yet is doing FPGA programming). + */ + ret = child_regions_with_firmware(overlay); + if (ret) + return ERR_PTR(ret); + + info = fpga_image_info_alloc(dev); + if (!info) + return ERR_PTR(-ENOMEM); + + info->overlay = overlay; + + /* Read FPGA region properties from the overlay */ + if (of_property_read_bool(overlay, "partial-fpga-config")) + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + if (of_property_read_bool(overlay, "external-fpga-config")) + info->flags |= FPGA_MGR_EXTERNAL_CONFIG; + + if (of_property_read_bool(overlay, "encrypted-fpga-config")) + info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM; + + if (!of_property_read_string(overlay, "firmware-name", + &firmware_name)) { + info->firmware_name = devm_kstrdup(dev, firmware_name, + GFP_KERNEL); + if (!info->firmware_name) + return ERR_PTR(-ENOMEM); + } + + of_property_read_u32(overlay, "region-unfreeze-timeout-us", + &info->enable_timeout_us); + + of_property_read_u32(overlay, "region-freeze-timeout-us", + &info->disable_timeout_us); + + of_property_read_u32(overlay, "config-complete-timeout-us", + &info->config_complete_timeout_us); + + /* If overlay is not programming the FPGA, don't need FPGA image info */ + if (!info->firmware_name) { + ret = 0; + goto ret_no_info; + } + + /* + * If overlay informs us FPGA was externally programmed, specifying + * firmware here would be ambiguous. + */ + if (info->flags & FPGA_MGR_EXTERNAL_CONFIG) { + dev_err(dev, "error: specified firmware and external-fpga-config"); + ret = -EINVAL; + goto ret_no_info; + } + + return info; +ret_no_info: + fpga_image_info_free(info); + return ERR_PTR(ret); +} + +/** + * of_fpga_region_notify_pre_apply - pre-apply overlay notification + * + * @region: FPGA region that the overlay was applied to + * @nd: overlay notification data + * + * Called when an overlay targeted to a FPGA Region is about to be applied. + * Parses the overlay for properties that influence how the FPGA will be + * programmed and does some checking. If the checks pass, programs the FPGA. + * If the checks fail, overlay is rejected and does not get added to the + * live tree. + * + * Returns 0 for success or negative error code for failure. + */ +static int of_fpga_region_notify_pre_apply(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + struct device *dev = ®ion->dev; + struct fpga_image_info *info; + int ret; + + info = of_fpga_region_parse_ov(region, nd->overlay); + if (IS_ERR(info)) + return PTR_ERR(info); + + /* If overlay doesn't program the FPGA, accept it anyway. */ + if (!info) + return 0; + + if (region->info) { + dev_err(dev, "Region already has overlay applied.\n"); + return -EINVAL; + } + + region->info = info; + ret = fpga_region_program_fpga(region); + if (ret) { + /* error; reject overlay */ + fpga_image_info_free(info); + region->info = NULL; + } + + return ret; +} + +/** + * of_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 of_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); + fpga_image_info_free(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 = of_fpga_region_find(nd->target); + if (!region) + return NOTIFY_OK; + + ret = 0; + switch (action) { + case OF_OVERLAY_PRE_APPLY: + ret = of_fpga_region_notify_pre_apply(region, nd); + break; + + case OF_OVERLAY_POST_REMOVE: + of_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 of_fpga_region_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct fpga_region *region; + struct fpga_manager *mgr; + int ret; + + /* Find the FPGA mgr specified by region or parent region. */ + mgr = of_fpga_region_get_mgr(np); + if (IS_ERR(mgr)) + return -EPROBE_DEFER; + + region = fpga_region_create(dev, mgr, of_fpga_region_get_bridges); + if (!region) { + ret = -ENOMEM; + goto eprobe_mgr_put; + } + + ret = fpga_region_register(region); + if (ret) + goto eprobe_free; + + of_platform_populate(np, fpga_region_of_match, NULL, ®ion->dev); + dev_set_drvdata(dev, region); + + dev_info(dev, "FPGA Region probed\n"); + + return 0; + +eprobe_free: + fpga_region_free(region); +eprobe_mgr_put: + fpga_mgr_put(mgr); + return ret; +} + +static int of_fpga_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = platform_get_drvdata(pdev); + struct fpga_manager *mgr = region->mgr; + + fpga_region_unregister(region); + fpga_mgr_put(mgr); + + return 0; +} + +static struct platform_driver of_fpga_region_driver = { + .probe = of_fpga_region_probe, + .remove = of_fpga_region_remove, + .driver = { + .name = "of-fpga-region", + .of_match_table = of_match_ptr(fpga_region_of_match), + }, +}; + +/** + * fpga_region_init - init function for fpga_region class + * Creates the fpga_region class and registers a reconfig notifier. + */ +static int __init of_fpga_region_init(void) +{ + int ret; + + ret = of_overlay_notifier_register(&fpga_region_of_nb); + if (ret) + return ret; + + ret = platform_driver_register(&of_fpga_region_driver); + if (ret) + goto err_plat; + + return 0; + +err_plat: + of_overlay_notifier_unregister(&fpga_region_of_nb); + return ret; +} + +static void __exit of_fpga_region_exit(void) +{ + platform_driver_unregister(&of_fpga_region_driver); + of_overlay_notifier_unregister(&fpga_region_of_nb); +} + +subsys_initcall(of_fpga_region_init); +module_exit(of_fpga_region_exit); + +MODULE_DESCRIPTION("FPGA Region"); +MODULE_AUTHOR("Alan Tull <atull@kernel.org>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/socfpga-a10.c b/drivers/fpga/socfpga-a10.c new file mode 100644 index 000000000..be30c48eb --- /dev/null +++ b/drivers/fpga/socfpga-a10.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Manager Driver for Altera Arria10 SoCFPGA + * + * Copyright (C) 2015-2016 Altera Corporation + */ +#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 fpga_manager *mgr; + 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; + } + + mgr = fpga_mgr_create(dev, "SoCFPGA Arria10 FPGA Manager", + &socfpga_a10_fpga_mgr_ops, priv); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) { + fpga_mgr_free(mgr); + clk_disable_unprepare(priv->clk); + return ret; + } + + return 0; +} + +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(mgr); + 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 new file mode 100644 index 000000000..959d71f26 --- /dev/null +++ b/drivers/fpga/socfpga.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Manager Driver for Altera SOCFPGA + * + * Copyright (C) 2013-2015 Altera Corporation + */ +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/pm.h> + +/* Register offsets */ +#define SOCFPGA_FPGMGR_STAT_OFST 0x0 +#define SOCFPGA_FPGMGR_CTL_OFST 0x4 +#define SOCFPGA_FPGMGR_DCLKCNT_OFST 0x8 +#define SOCFPGA_FPGMGR_DCLKSTAT_OFST 0xc +#define SOCFPGA_FPGMGR_GPIO_INTEN_OFST 0x830 +#define SOCFPGA_FPGMGR_GPIO_INTMSK_OFST 0x834 +#define SOCFPGA_FPGMGR_GPIO_INTTYPE_LEVEL_OFST 0x838 +#define SOCFPGA_FPGMGR_GPIO_INT_POL_OFST 0x83c +#define SOCFPGA_FPGMGR_GPIO_INTSTAT_OFST 0x840 +#define SOCFPGA_FPGMGR_GPIO_RAW_INTSTAT_OFST 0x844 +#define SOCFPGA_FPGMGR_GPIO_PORTA_EOI_OFST 0x84c +#define SOCFPGA_FPGMGR_GPIO_EXT_PORTA_OFST 0x850 + +/* Register bit defines */ +/* SOCFPGA_FPGMGR_STAT register mode field values */ +#define SOCFPGA_FPGMGR_STAT_POWER_UP 0x0 /*ramping*/ +#define SOCFPGA_FPGMGR_STAT_RESET 0x1 +#define SOCFPGA_FPGMGR_STAT_CFG 0x2 +#define SOCFPGA_FPGMGR_STAT_INIT 0x3 +#define SOCFPGA_FPGMGR_STAT_USER_MODE 0x4 +#define SOCFPGA_FPGMGR_STAT_UNKNOWN 0x5 +#define SOCFPGA_FPGMGR_STAT_STATE_MASK 0x7 +/* This is a flag value that doesn't really happen in this register field */ +#define SOCFPGA_FPGMGR_STAT_POWER_OFF 0x0 + +#define MSEL_PP16_FAST_NOAES_NODC 0x0 +#define MSEL_PP16_FAST_AES_NODC 0x1 +#define MSEL_PP16_FAST_AESOPT_DC 0x2 +#define MSEL_PP16_SLOW_NOAES_NODC 0x4 +#define MSEL_PP16_SLOW_AES_NODC 0x5 +#define MSEL_PP16_SLOW_AESOPT_DC 0x6 +#define MSEL_PP32_FAST_NOAES_NODC 0x8 +#define MSEL_PP32_FAST_AES_NODC 0x9 +#define MSEL_PP32_FAST_AESOPT_DC 0xa +#define MSEL_PP32_SLOW_NOAES_NODC 0xc +#define MSEL_PP32_SLOW_AES_NODC 0xd +#define MSEL_PP32_SLOW_AESOPT_DC 0xe +#define SOCFPGA_FPGMGR_STAT_MSEL_MASK 0x000000f8 +#define SOCFPGA_FPGMGR_STAT_MSEL_SHIFT 3 + +/* SOCFPGA_FPGMGR_CTL register */ +#define SOCFPGA_FPGMGR_CTL_EN 0x00000001 +#define SOCFPGA_FPGMGR_CTL_NCE 0x00000002 +#define SOCFPGA_FPGMGR_CTL_NCFGPULL 0x00000004 + +#define CDRATIO_X1 0x00000000 +#define CDRATIO_X2 0x00000040 +#define CDRATIO_X4 0x00000080 +#define CDRATIO_X8 0x000000c0 +#define SOCFPGA_FPGMGR_CTL_CDRATIO_MASK 0x000000c0 + +#define SOCFPGA_FPGMGR_CTL_AXICFGEN 0x00000100 + +#define CFGWDTH_16 0x00000000 +#define CFGWDTH_32 0x00000200 +#define SOCFPGA_FPGMGR_CTL_CFGWDTH_MASK 0x00000200 + +/* SOCFPGA_FPGMGR_DCLKSTAT register */ +#define SOCFPGA_FPGMGR_DCLKSTAT_DCNTDONE_E_DONE 0x1 + +/* SOCFPGA_FPGMGR_GPIO_* registers share the same bit positions */ +#define SOCFPGA_FPGMGR_MON_NSTATUS 0x0001 +#define SOCFPGA_FPGMGR_MON_CONF_DONE 0x0002 +#define SOCFPGA_FPGMGR_MON_INIT_DONE 0x0004 +#define SOCFPGA_FPGMGR_MON_CRC_ERROR 0x0008 +#define SOCFPGA_FPGMGR_MON_CVP_CONF_DONE 0x0010 +#define SOCFPGA_FPGMGR_MON_PR_READY 0x0020 +#define SOCFPGA_FPGMGR_MON_PR_ERROR 0x0040 +#define SOCFPGA_FPGMGR_MON_PR_DONE 0x0080 +#define SOCFPGA_FPGMGR_MON_NCONFIG_PIN 0x0100 +#define SOCFPGA_FPGMGR_MON_NSTATUS_PIN 0x0200 +#define SOCFPGA_FPGMGR_MON_CONF_DONE_PIN 0x0400 +#define SOCFPGA_FPGMGR_MON_FPGA_POWER_ON 0x0800 +#define SOCFPGA_FPGMGR_MON_STATUS_MASK 0x0fff + +#define SOCFPGA_FPGMGR_NUM_SUPPLIES 3 +#define SOCFPGA_RESUME_TIMEOUT 3 + +/* In power-up order. Reverse for power-down. */ +static const char *supply_names[SOCFPGA_FPGMGR_NUM_SUPPLIES] __maybe_unused = { + "FPGA-1.5V", + "FPGA-1.1V", + "FPGA-2.5V", +}; + +struct socfpga_fpga_priv { + void __iomem *fpga_base_addr; + void __iomem *fpga_data_addr; + struct completion status_complete; + int irq; +}; + +struct cfgmgr_mode { + /* Values to set in the CTRL register */ + u32 ctrl; + + /* flag that this table entry is a valid mode */ + bool valid; +}; + +/* For SOCFPGA_FPGMGR_STAT_MSEL field */ +static struct cfgmgr_mode cfgmgr_modes[] = { + [MSEL_PP16_FAST_NOAES_NODC] = { CFGWDTH_16 | CDRATIO_X1, 1 }, + [MSEL_PP16_FAST_AES_NODC] = { CFGWDTH_16 | CDRATIO_X2, 1 }, + [MSEL_PP16_FAST_AESOPT_DC] = { CFGWDTH_16 | CDRATIO_X4, 1 }, + [MSEL_PP16_SLOW_NOAES_NODC] = { CFGWDTH_16 | CDRATIO_X1, 1 }, + [MSEL_PP16_SLOW_AES_NODC] = { CFGWDTH_16 | CDRATIO_X2, 1 }, + [MSEL_PP16_SLOW_AESOPT_DC] = { CFGWDTH_16 | CDRATIO_X4, 1 }, + [MSEL_PP32_FAST_NOAES_NODC] = { CFGWDTH_32 | CDRATIO_X1, 1 }, + [MSEL_PP32_FAST_AES_NODC] = { CFGWDTH_32 | CDRATIO_X4, 1 }, + [MSEL_PP32_FAST_AESOPT_DC] = { CFGWDTH_32 | CDRATIO_X8, 1 }, + [MSEL_PP32_SLOW_NOAES_NODC] = { CFGWDTH_32 | CDRATIO_X1, 1 }, + [MSEL_PP32_SLOW_AES_NODC] = { CFGWDTH_32 | CDRATIO_X4, 1 }, + [MSEL_PP32_SLOW_AESOPT_DC] = { CFGWDTH_32 | CDRATIO_X8, 1 }, +}; + +static u32 socfpga_fpga_readl(struct socfpga_fpga_priv *priv, u32 reg_offset) +{ + return readl(priv->fpga_base_addr + reg_offset); +} + +static void socfpga_fpga_writel(struct socfpga_fpga_priv *priv, u32 reg_offset, + u32 value) +{ + writel(value, priv->fpga_base_addr + reg_offset); +} + +static u32 socfpga_fpga_raw_readl(struct socfpga_fpga_priv *priv, + u32 reg_offset) +{ + return __raw_readl(priv->fpga_base_addr + reg_offset); +} + +static void socfpga_fpga_raw_writel(struct socfpga_fpga_priv *priv, + u32 reg_offset, u32 value) +{ + __raw_writel(value, priv->fpga_base_addr + reg_offset); +} + +static void socfpga_fpga_data_writel(struct socfpga_fpga_priv *priv, u32 value) +{ + writel(value, priv->fpga_data_addr); +} + +static inline void socfpga_fpga_set_bitsl(struct socfpga_fpga_priv *priv, + u32 offset, u32 bits) +{ + u32 val; + + val = socfpga_fpga_readl(priv, offset); + val |= bits; + socfpga_fpga_writel(priv, offset, val); +} + +static inline void socfpga_fpga_clr_bitsl(struct socfpga_fpga_priv *priv, + u32 offset, u32 bits) +{ + u32 val; + + val = socfpga_fpga_readl(priv, offset); + val &= ~bits; + socfpga_fpga_writel(priv, offset, val); +} + +static u32 socfpga_fpga_mon_status_get(struct socfpga_fpga_priv *priv) +{ + return socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_GPIO_EXT_PORTA_OFST) & + SOCFPGA_FPGMGR_MON_STATUS_MASK; +} + +static u32 socfpga_fpga_state_get(struct socfpga_fpga_priv *priv) +{ + u32 status = socfpga_fpga_mon_status_get(priv); + + if ((status & SOCFPGA_FPGMGR_MON_FPGA_POWER_ON) == 0) + return SOCFPGA_FPGMGR_STAT_POWER_OFF; + + return socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_STAT_OFST) & + SOCFPGA_FPGMGR_STAT_STATE_MASK; +} + +static void socfpga_fpga_clear_done_status(struct socfpga_fpga_priv *priv) +{ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_DCLKSTAT_OFST, + SOCFPGA_FPGMGR_DCLKSTAT_DCNTDONE_E_DONE); +} + +/* + * Set the DCLKCNT, wait for DCLKSTAT to report the count completed, and clear + * the complete status. + */ +static int socfpga_fpga_dclk_set_and_wait_clear(struct socfpga_fpga_priv *priv, + u32 count) +{ + int timeout = 2; + u32 done; + + /* Clear any existing DONE status. */ + if (socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_DCLKSTAT_OFST)) + socfpga_fpga_clear_done_status(priv); + + /* Issue the DCLK count. */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_DCLKCNT_OFST, count); + + /* Poll DCLKSTAT to see if it completed in the timeout period. */ + do { + done = socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_DCLKSTAT_OFST); + if (done == SOCFPGA_FPGMGR_DCLKSTAT_DCNTDONE_E_DONE) { + socfpga_fpga_clear_done_status(priv); + return 0; + } + udelay(1); + } while (timeout--); + + return -ETIMEDOUT; +} + +static int socfpga_fpga_wait_for_state(struct socfpga_fpga_priv *priv, + u32 state) +{ + int timeout = 2; + + /* + * HW doesn't support an interrupt for changes in state, so poll to see + * if it matches the requested state within the timeout period. + */ + do { + if ((socfpga_fpga_state_get(priv) & state) != 0) + return 0; + msleep(20); + } while (timeout--); + + return -ETIMEDOUT; +} + +static void socfpga_fpga_enable_irqs(struct socfpga_fpga_priv *priv, u32 irqs) +{ + /* set irqs to level sensitive */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_INTTYPE_LEVEL_OFST, 0); + + /* set interrupt polarity */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_INT_POL_OFST, irqs); + + /* clear irqs */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_PORTA_EOI_OFST, irqs); + + /* unmask interrupts */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_INTMSK_OFST, 0); + + /* enable interrupts */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_INTEN_OFST, irqs); +} + +static void socfpga_fpga_disable_irqs(struct socfpga_fpga_priv *priv) +{ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_INTEN_OFST, 0); +} + +static irqreturn_t socfpga_fpga_isr(int irq, void *dev_id) +{ + struct socfpga_fpga_priv *priv = dev_id; + u32 irqs, st; + bool conf_done, nstatus; + + /* clear irqs */ + irqs = socfpga_fpga_raw_readl(priv, SOCFPGA_FPGMGR_GPIO_INTSTAT_OFST); + + socfpga_fpga_raw_writel(priv, SOCFPGA_FPGMGR_GPIO_PORTA_EOI_OFST, irqs); + + st = socfpga_fpga_raw_readl(priv, SOCFPGA_FPGMGR_GPIO_EXT_PORTA_OFST); + conf_done = (st & SOCFPGA_FPGMGR_MON_CONF_DONE) != 0; + nstatus = (st & SOCFPGA_FPGMGR_MON_NSTATUS) != 0; + + /* success */ + if (conf_done && nstatus) { + /* disable irqs */ + socfpga_fpga_raw_writel(priv, + SOCFPGA_FPGMGR_GPIO_INTEN_OFST, 0); + complete(&priv->status_complete); + } + + return IRQ_HANDLED; +} + +static int socfpga_fpga_wait_for_config_done(struct socfpga_fpga_priv *priv) +{ + int timeout, ret = 0; + + socfpga_fpga_disable_irqs(priv); + init_completion(&priv->status_complete); + socfpga_fpga_enable_irqs(priv, SOCFPGA_FPGMGR_MON_CONF_DONE); + + timeout = wait_for_completion_interruptible_timeout( + &priv->status_complete, + msecs_to_jiffies(10)); + if (timeout == 0) + ret = -ETIMEDOUT; + + socfpga_fpga_disable_irqs(priv); + return ret; +} + +static int socfpga_fpga_cfg_mode_get(struct socfpga_fpga_priv *priv) +{ + u32 msel; + + msel = socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_STAT_OFST); + msel &= SOCFPGA_FPGMGR_STAT_MSEL_MASK; + msel >>= SOCFPGA_FPGMGR_STAT_MSEL_SHIFT; + + /* Check that this MSEL setting is supported */ + if ((msel >= ARRAY_SIZE(cfgmgr_modes)) || !cfgmgr_modes[msel].valid) + return -EINVAL; + + return msel; +} + +static int socfpga_fpga_cfg_mode_set(struct socfpga_fpga_priv *priv) +{ + u32 ctrl_reg; + int mode; + + /* get value from MSEL pins */ + mode = socfpga_fpga_cfg_mode_get(priv); + if (mode < 0) + return mode; + + /* Adjust CTRL for the CDRATIO */ + ctrl_reg = socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_CTL_OFST); + ctrl_reg &= ~SOCFPGA_FPGMGR_CTL_CDRATIO_MASK; + ctrl_reg &= ~SOCFPGA_FPGMGR_CTL_CFGWDTH_MASK; + ctrl_reg |= cfgmgr_modes[mode].ctrl; + + /* Set NCE to 0. */ + ctrl_reg &= ~SOCFPGA_FPGMGR_CTL_NCE; + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_CTL_OFST, ctrl_reg); + + return 0; +} + +static int socfpga_fpga_reset(struct fpga_manager *mgr) +{ + struct socfpga_fpga_priv *priv = mgr->priv; + u32 ctrl_reg, status; + int ret; + + /* + * Step 1: + * - Set CTRL.CFGWDTH, CTRL.CDRATIO to match cfg mode + * - Set CTRL.NCE to 0 + */ + ret = socfpga_fpga_cfg_mode_set(priv); + if (ret) + return ret; + + /* Step 2: Set CTRL.EN to 1 */ + socfpga_fpga_set_bitsl(priv, SOCFPGA_FPGMGR_CTL_OFST, + SOCFPGA_FPGMGR_CTL_EN); + + /* Step 3: Set CTRL.NCONFIGPULL to 1 to put FPGA in reset */ + ctrl_reg = socfpga_fpga_readl(priv, SOCFPGA_FPGMGR_CTL_OFST); + ctrl_reg |= SOCFPGA_FPGMGR_CTL_NCFGPULL; + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_CTL_OFST, ctrl_reg); + + /* Step 4: Wait for STATUS.MODE to report FPGA is in reset phase */ + status = socfpga_fpga_wait_for_state(priv, SOCFPGA_FPGMGR_STAT_RESET); + + /* Step 5: Set CONTROL.NCONFIGPULL to 0 to release FPGA from reset */ + ctrl_reg &= ~SOCFPGA_FPGMGR_CTL_NCFGPULL; + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_CTL_OFST, ctrl_reg); + + /* Timeout waiting for reset */ + if (status) + return -ETIMEDOUT; + + return 0; +} + +/* + * Prepare the FPGA to receive the configuration data. + */ +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 (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + /* Steps 1 - 5: Reset the FPGA */ + ret = socfpga_fpga_reset(mgr); + if (ret) + return ret; + + /* Step 6: Wait for FPGA to enter configuration phase */ + if (socfpga_fpga_wait_for_state(priv, SOCFPGA_FPGMGR_STAT_CFG)) + return -ETIMEDOUT; + + /* Step 7: Clear nSTATUS interrupt */ + socfpga_fpga_writel(priv, SOCFPGA_FPGMGR_GPIO_PORTA_EOI_OFST, + SOCFPGA_FPGMGR_MON_NSTATUS); + + /* Step 8: Set CTRL.AXICFGEN to 1 to enable transfer of config data */ + socfpga_fpga_set_bitsl(priv, SOCFPGA_FPGMGR_CTL_OFST, + SOCFPGA_FPGMGR_CTL_AXICFGEN); + + return 0; +} + +/* + * Step 9: write data to the FPGA data register + */ +static int socfpga_fpga_ops_configure_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct socfpga_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)) { + socfpga_fpga_data_writel(priv, buffer_32[i++]); + count -= sizeof(u32); + } + + /* Write out remaining non 32-bit chunks. */ + switch (count) { + case 3: + socfpga_fpga_data_writel(priv, buffer_32[i++] & 0x00ffffff); + break; + case 2: + socfpga_fpga_data_writel(priv, buffer_32[i++] & 0x0000ffff); + break; + case 1: + socfpga_fpga_data_writel(priv, buffer_32[i++] & 0x000000ff); + break; + case 0: + break; + default: + /* This will never happen. */ + return -EFAULT; + } + + return 0; +} + +static int socfpga_fpga_ops_configure_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct socfpga_fpga_priv *priv = mgr->priv; + u32 status; + + /* + * Step 10: + * - Observe CONF_DONE and nSTATUS (active low) + * - if CONF_DONE = 1 and nSTATUS = 1, configuration was successful + * - if CONF_DONE = 0 and nSTATUS = 0, configuration failed + */ + status = socfpga_fpga_wait_for_config_done(priv); + if (status) + return status; + + /* Step 11: Clear CTRL.AXICFGEN to disable transfer of config data */ + socfpga_fpga_clr_bitsl(priv, SOCFPGA_FPGMGR_CTL_OFST, + SOCFPGA_FPGMGR_CTL_AXICFGEN); + + /* + * Step 12: + * - Write 4 to DCLKCNT + * - Wait for STATUS.DCNTDONE = 1 + * - Clear W1C bit in STATUS.DCNTDONE + */ + if (socfpga_fpga_dclk_set_and_wait_clear(priv, 4)) + return -ETIMEDOUT; + + /* Step 13: Wait for STATUS.MODE to report USER MODE */ + if (socfpga_fpga_wait_for_state(priv, SOCFPGA_FPGMGR_STAT_USER_MODE)) + return -ETIMEDOUT; + + /* Step 14: Set CTRL.EN to 0 */ + socfpga_fpga_clr_bitsl(priv, SOCFPGA_FPGMGR_CTL_OFST, + SOCFPGA_FPGMGR_CTL_EN); + + return 0; +} + +/* Translate state register values to FPGA framework state */ +static const enum fpga_mgr_states socfpga_state_to_framework_state[] = { + [SOCFPGA_FPGMGR_STAT_POWER_OFF] = FPGA_MGR_STATE_POWER_OFF, + [SOCFPGA_FPGMGR_STAT_RESET] = FPGA_MGR_STATE_RESET, + [SOCFPGA_FPGMGR_STAT_CFG] = FPGA_MGR_STATE_WRITE_INIT, + [SOCFPGA_FPGMGR_STAT_INIT] = FPGA_MGR_STATE_WRITE_INIT, + [SOCFPGA_FPGMGR_STAT_USER_MODE] = FPGA_MGR_STATE_OPERATING, + [SOCFPGA_FPGMGR_STAT_UNKNOWN] = FPGA_MGR_STATE_UNKNOWN, +}; + +static enum fpga_mgr_states socfpga_fpga_ops_state(struct fpga_manager *mgr) +{ + struct socfpga_fpga_priv *priv = mgr->priv; + enum fpga_mgr_states ret; + u32 state; + + state = socfpga_fpga_state_get(priv); + + if (state < ARRAY_SIZE(socfpga_state_to_framework_state)) + ret = socfpga_state_to_framework_state[state]; + else + ret = FPGA_MGR_STATE_UNKNOWN; + + return ret; +} + +static const struct fpga_manager_ops socfpga_fpga_ops = { + .state = socfpga_fpga_ops_state, + .write_init = socfpga_fpga_ops_configure_init, + .write = socfpga_fpga_ops_configure_write, + .write_complete = socfpga_fpga_ops_configure_complete, +}; + +static int socfpga_fpga_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct socfpga_fpga_priv *priv; + struct fpga_manager *mgr; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->fpga_base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->fpga_base_addr)) + return PTR_ERR(priv->fpga_base_addr); + + 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); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) + return priv->irq; + + ret = devm_request_irq(dev, priv->irq, socfpga_fpga_isr, 0, + dev_name(dev), priv); + if (ret) + return ret; + + mgr = fpga_mgr_create(dev, "Altera SOCFPGA FPGA Manager", + &socfpga_fpga_ops, priv); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int socfpga_fpga_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + fpga_mgr_unregister(mgr); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id socfpga_fpga_of_match[] = { + { .compatible = "altr,socfpga-fpga-mgr", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, socfpga_fpga_of_match); +#endif + +static struct platform_driver socfpga_fpga_driver = { + .probe = socfpga_fpga_probe, + .remove = socfpga_fpga_remove, + .driver = { + .name = "socfpga_fpga_manager", + .of_match_table = of_match_ptr(socfpga_fpga_of_match), + }, +}; + +module_platform_driver(socfpga_fpga_driver); + +MODULE_AUTHOR("Alan Tull <atull@opensource.altera.com>"); +MODULE_DESCRIPTION("Altera SOCFPGA FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/ts73xx-fpga.c b/drivers/fpga/ts73xx-fpga.c new file mode 100644 index 000000000..08efd1895 --- /dev/null +++ b/drivers/fpga/ts73xx-fpga.c @@ -0,0 +1,170 @@ +/* + * Technologic Systems TS-73xx SBC FPGA loader + * + * Copyright (C) 2016 Florian Fainelli <f.fainelli@gmail.com> + * + * FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on + * TS-7300, heavily based on load_fpga.c in their vendor tree. + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/iopoll.h> +#include <linux/fpga/fpga-mgr.h> + +#define TS73XX_FPGA_DATA_REG 0 +#define TS73XX_FPGA_CONFIG_REG 1 + +#define TS73XX_FPGA_WRITE_DONE 0x1 +#define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */ +#define TS73XX_FPGA_RESET 0x2 +#define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */ +#define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */ +#define TS73XX_FPGA_LOAD_OK 0x4 +#define TS73XX_FPGA_CONFIG_LOAD 0x8 + +struct ts73xx_fpga_priv { + void __iomem *io_base; + struct device *dev; +}; + +static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static int ts73xx_fpga_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + + /* Reset the FPGA */ + writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG); + udelay(TS73XX_FPGA_RESET_LOW_DELAY); + writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG); + udelay(TS73XX_FPGA_RESET_HIGH_DELAY); + + return 0; +} + +static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + size_t i = 0; + int ret; + u8 reg; + + while (count--) { + ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG, + reg, !(reg & TS73XX_FPGA_WRITE_DONE), + 1, TS73XX_FPGA_WRITE_DONE_TIMEOUT); + if (ret < 0) + return ret; + + writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG); + i++; + } + + return 0; +} + +static int ts73xx_fpga_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct ts73xx_fpga_priv *priv = mgr->priv; + u8 reg; + + usleep_range(1000, 2000); + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + reg |= TS73XX_FPGA_CONFIG_LOAD; + writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); + + usleep_range(1000, 2000); + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + reg &= ~TS73XX_FPGA_CONFIG_LOAD; + writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG); + + reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG); + if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK) + return -ETIMEDOUT; + + return 0; +} + +static const struct fpga_manager_ops ts73xx_fpga_ops = { + .state = ts73xx_fpga_state, + .write_init = ts73xx_fpga_write_init, + .write = ts73xx_fpga_write, + .write_complete = ts73xx_fpga_write_complete, +}; + +static int ts73xx_fpga_probe(struct platform_device *pdev) +{ + struct device *kdev = &pdev->dev; + struct ts73xx_fpga_priv *priv; + struct fpga_manager *mgr; + struct resource *res; + int ret; + + priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = kdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(kdev, res); + if (IS_ERR(priv->io_base)) { + dev_err(kdev, "unable to remap registers\n"); + return PTR_ERR(priv->io_base); + } + + mgr = fpga_mgr_create(kdev, "TS-73xx FPGA Manager", + &ts73xx_fpga_ops, priv); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int ts73xx_fpga_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static struct platform_driver ts73xx_fpga_driver = { + .driver = { + .name = "ts73xx-fpga-mgr", + }, + .probe = ts73xx_fpga_probe, + .remove = ts73xx_fpga_remove, +}; +module_platform_driver(ts73xx_fpga_driver); + +MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>"); +MODULE_DESCRIPTION("TS-73xx FPGA Manager driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/xilinx-pr-decoupler.c b/drivers/fpga/xilinx-pr-decoupler.c new file mode 100644 index 000000000..07ba1539e --- /dev/null +++ b/drivers/fpga/xilinx-pr-decoupler.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2017, National Instruments Corp. + * Copyright (c) 2017, Xilix Inc + * + * FPGA Bridge Driver for the Xilinx LogiCORE Partial Reconfiguration + * Decoupler IP Core. + * + * 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. + */ + +#include <linux/clk.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 CTRL_CMD_DECOUPLE BIT(0) +#define CTRL_CMD_COUPLE 0 +#define CTRL_OFFSET 0 + +struct xlnx_pr_decoupler_data { + void __iomem *io_base; + struct clk *clk; +}; + +static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d, + u32 offset, u32 val) +{ + writel(val, d->io_base + offset); +} + +static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d, + u32 offset) +{ + return readl(d->io_base + offset); +} + +static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable) +{ + int err; + struct xlnx_pr_decoupler_data *priv = bridge->priv; + + err = clk_enable(priv->clk); + if (err) + return err; + + if (enable) + xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE); + else + xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE); + + clk_disable(priv->clk); + + return 0; +} + +static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge) +{ + const struct xlnx_pr_decoupler_data *priv = bridge->priv; + u32 status; + int err; + + err = clk_enable(priv->clk); + if (err) + return err; + + status = readl(priv->io_base); + + clk_disable(priv->clk); + + return !status; +} + +static const struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = { + .enable_set = xlnx_pr_decoupler_enable_set, + .enable_show = xlnx_pr_decoupler_enable_show, +}; + +static const struct of_device_id xlnx_pr_decoupler_of_match[] = { + { .compatible = "xlnx,pr-decoupler-1.00", }, + { .compatible = "xlnx,pr-decoupler", }, + {}, +}; +MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match); + +static int xlnx_pr_decoupler_probe(struct platform_device *pdev) +{ + struct xlnx_pr_decoupler_data *priv; + struct fpga_bridge *br; + int err; + struct resource *res; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->io_base)) + return PTR_ERR(priv->io_base); + + priv->clk = devm_clk_get(&pdev->dev, "aclk"); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "input clock not found\n"); + return PTR_ERR(priv->clk); + } + + err = clk_prepare_enable(priv->clk); + if (err) { + dev_err(&pdev->dev, "unable to enable clock\n"); + return err; + } + + clk_disable(priv->clk); + + br = fpga_bridge_create(&pdev->dev, "Xilinx PR Decoupler", + &xlnx_pr_decoupler_br_ops, priv); + if (!br) { + err = -ENOMEM; + goto err_clk; + } + + platform_set_drvdata(pdev, br); + + err = fpga_bridge_register(br); + if (err) { + dev_err(&pdev->dev, "unable to register Xilinx PR Decoupler"); + goto err_clk; + } + + return 0; + +err_clk: + clk_unprepare(priv->clk); + + return err; +} + +static int xlnx_pr_decoupler_remove(struct platform_device *pdev) +{ + struct fpga_bridge *bridge = platform_get_drvdata(pdev); + struct xlnx_pr_decoupler_data *p = bridge->priv; + + fpga_bridge_unregister(bridge); + + clk_unprepare(p->clk); + + return 0; +} + +static struct platform_driver xlnx_pr_decoupler_driver = { + .probe = xlnx_pr_decoupler_probe, + .remove = xlnx_pr_decoupler_remove, + .driver = { + .name = "xlnx_pr_decoupler", + .of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match), + }, +}; + +module_platform_driver(xlnx_pr_decoupler_driver); + +MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler"); +MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>"); +MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/xilinx-spi.c b/drivers/fpga/xilinx-spi.c new file mode 100644 index 000000000..8d1945966 --- /dev/null +++ b/drivers/fpga/xilinx-spi.c @@ -0,0 +1,212 @@ +/* + * Xilinx Spartan6 Slave Serial SPI Driver + * + * 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 and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * Manage Xilinx FPGA firmware that is loaded over SPI using + * the slave serial configuration interface. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/of.h> +#include <linux/spi/spi.h> +#include <linux/sizes.h> + +struct xilinx_spi_conf { + struct spi_device *spi; + struct gpio_desc *prog_b; + struct gpio_desc *done; +}; + +static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) +{ + struct xilinx_spi_conf *conf = mgr->priv; + + if (!gpiod_get_value(conf->done)) + return FPGA_MGR_STATE_RESET; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static int xilinx_spi_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct xilinx_spi_conf *conf = mgr->priv; + const size_t prog_latency_7500us = 7500; + const size_t prog_pulse_1us = 1; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + return -EINVAL; + } + + gpiod_set_value(conf->prog_b, 1); + + udelay(prog_pulse_1us); /* min is 500 ns */ + + gpiod_set_value(conf->prog_b, 0); + + if (gpiod_get_value(conf->done)) { + dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); + return -EIO; + } + + /* program latency */ + usleep_range(prog_latency_7500us, prog_latency_7500us + 100); + return 0; +} + +static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct xilinx_spi_conf *conf = mgr->priv; + const char *fw_data = buf; + const char *fw_data_end = fw_data + count; + + while (fw_data < fw_data_end) { + size_t remaining, stride; + int ret; + + remaining = fw_data_end - fw_data; + stride = min_t(size_t, remaining, SZ_4K); + + 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 xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf) +{ + struct spi_device *spi = conf->spi; + const u8 din_data[1] = { 0xff }; + int ret; + + ret = spi_write(conf->spi, din_data, sizeof(din_data)); + if (ret) + dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret); + + return ret; +} + +static int xilinx_spi_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct xilinx_spi_conf *conf = mgr->priv; + unsigned long timeout; + int ret; + + if (gpiod_get_value(conf->done)) + return xilinx_spi_apply_cclk_cycles(conf); + + timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + + while (time_before(jiffies, timeout)) { + + ret = xilinx_spi_apply_cclk_cycles(conf); + if (ret) + return ret; + + if (gpiod_get_value(conf->done)) + return xilinx_spi_apply_cclk_cycles(conf); + } + + dev_err(&mgr->dev, "Timeout after config data transfer.\n"); + return -ETIMEDOUT; +} + +static const struct fpga_manager_ops xilinx_spi_ops = { + .state = xilinx_spi_state, + .write_init = xilinx_spi_write_init, + .write = xilinx_spi_write, + .write_complete = xilinx_spi_write_complete, +}; + +static int xilinx_spi_probe(struct spi_device *spi) +{ + struct xilinx_spi_conf *conf; + struct fpga_manager *mgr; + int ret; + + conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL); + if (!conf) + return -ENOMEM; + + conf->spi = spi; + + /* PROGRAM_B is active low */ + conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW); + if (IS_ERR(conf->prog_b)) { + dev_err(&spi->dev, "Failed to get PROGRAM_B gpio: %ld\n", + PTR_ERR(conf->prog_b)); + return PTR_ERR(conf->prog_b); + } + + conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN); + if (IS_ERR(conf->done)) { + dev_err(&spi->dev, "Failed to get DONE gpio: %ld\n", + PTR_ERR(conf->done)); + return PTR_ERR(conf->done); + } + + mgr = fpga_mgr_create(&spi->dev, "Xilinx Slave Serial FPGA Manager", + &xilinx_spi_ops, conf); + if (!mgr) + return -ENOMEM; + + spi_set_drvdata(spi, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int xilinx_spi_remove(struct spi_device *spi) +{ + struct fpga_manager *mgr = spi_get_drvdata(spi); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static const struct of_device_id xlnx_spi_of_match[] = { + { .compatible = "xlnx,fpga-slave-serial", }, + {} +}; +MODULE_DEVICE_TABLE(of, xlnx_spi_of_match); + +static struct spi_driver xilinx_slave_spi_driver = { + .driver = { + .name = "xlnx-slave-spi", + .of_match_table = of_match_ptr(xlnx_spi_of_match), + }, + .probe = xilinx_spi_probe, + .remove = xilinx_spi_remove, +}; + +module_spi_driver(xilinx_slave_spi_driver) + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>"); +MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SPI"); diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c new file mode 100644 index 000000000..3110e0012 --- /dev/null +++ b/drivers/fpga/zynq-fpga.c @@ -0,0 +1,673 @@ +/* + * Copyright (c) 2011-2015 Xilinx Inc. + * Copyright (c) 2015, National Instruments Corp. + * + * FPGA Manager Driver for Xilinx Zynq, heavily based on xdevcfg driver + * in their vendor tree. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/string.h> +#include <linux/scatterlist.h> + +/* Offsets into SLCR regmap */ + +/* FPGA Software Reset Control */ +#define SLCR_FPGA_RST_CTRL_OFFSET 0x240 +/* Level Shifters Enable */ +#define SLCR_LVL_SHFTR_EN_OFFSET 0x900 + +/* Constant Definitions */ + +/* Control Register */ +#define CTRL_OFFSET 0x00 +/* Lock Register */ +#define LOCK_OFFSET 0x04 +/* Interrupt Status Register */ +#define INT_STS_OFFSET 0x0c +/* Interrupt Mask Register */ +#define INT_MASK_OFFSET 0x10 +/* Status Register */ +#define STATUS_OFFSET 0x14 +/* DMA Source Address Register */ +#define DMA_SRC_ADDR_OFFSET 0x18 +/* DMA Destination Address Reg */ +#define DMA_DST_ADDR_OFFSET 0x1c +/* DMA Source Transfer Length */ +#define DMA_SRC_LEN_OFFSET 0x20 +/* DMA Destination Transfer */ +#define DMA_DEST_LEN_OFFSET 0x24 +/* Unlock Register */ +#define UNLOCK_OFFSET 0x34 +/* Misc. Control Register */ +#define MCTRL_OFFSET 0x80 + +/* Control Register Bit definitions */ + +/* Signal to reset FPGA */ +#define CTRL_PCFG_PROG_B_MASK BIT(30) +/* Enable PCAP for PR */ +#define CTRL_PCAP_PR_MASK BIT(27) +/* Enable PCAP */ +#define CTRL_PCAP_MODE_MASK BIT(26) +/* Lower rate to allow decrypt on the fly */ +#define CTRL_PCAP_RATE_EN_MASK BIT(25) +/* System booted in secure mode */ +#define CTRL_SEC_EN_MASK BIT(7) + +/* Miscellaneous Control Register bit definitions */ +/* Internal PCAP loopback */ +#define MCTRL_PCAP_LPBK_MASK BIT(4) + +/* Status register bit definitions */ + +/* FPGA init status */ +#define STATUS_DMA_Q_F BIT(31) +#define STATUS_DMA_Q_E BIT(30) +#define STATUS_PCFG_INIT_MASK BIT(4) + +/* Interrupt Status/Mask Register Bit definitions */ +/* DMA command done */ +#define IXR_DMA_DONE_MASK BIT(13) +/* DMA and PCAP cmd done */ +#define IXR_D_P_DONE_MASK BIT(12) + /* FPGA programmed */ +#define IXR_PCFG_DONE_MASK BIT(2) +#define IXR_ERROR_FLAGS_MASK 0x00F0C860 +#define IXR_ALL_MASK 0xF8F7F87F + +/* Miscellaneous constant values */ + +/* Invalid DMA addr */ +#define DMA_INVALID_ADDRESS GENMASK(31, 0) +/* Used to unlock the dev */ +#define UNLOCK_MASK 0x757bdf0d +/* Timeout for polling reset bits */ +#define INIT_POLL_TIMEOUT 2500000 +/* Delay for polling reset bits */ +#define INIT_POLL_DELAY 20 +/* Signal this is the last DMA transfer, wait for the AXI and PCAP before + * interrupting + */ +#define DMA_SRC_LAST_TRANSFER 1 +/* Timeout for DMA completion */ +#define DMA_TIMEOUT_MS 5000 + +/* Masks for controlling stuff in SLCR */ +/* Disable all Level shifters */ +#define LVL_SHFTR_DISABLE_ALL_MASK 0x0 +/* Enable Level shifters from PS to PL */ +#define LVL_SHFTR_ENABLE_PS_TO_PL 0xa +/* Enable Level shifters from PL to PS */ +#define LVL_SHFTR_ENABLE_PL_TO_PS 0xf +/* Enable global resets */ +#define FPGA_RST_ALL_MASK 0xf +/* Disable global resets */ +#define FPGA_RST_NONE_MASK 0x0 + +struct zynq_fpga_priv { + int irq; + struct clk *clk; + + void __iomem *io_base; + struct regmap *slcr; + + spinlock_t dma_lock; + unsigned int dma_elm; + unsigned int dma_nelms; + struct scatterlist *cur_sg; + + struct completion dma_done; +}; + +static inline void zynq_fpga_write(struct zynq_fpga_priv *priv, u32 offset, + u32 val) +{ + writel(val, priv->io_base + offset); +} + +static inline u32 zynq_fpga_read(const struct zynq_fpga_priv *priv, + u32 offset) +{ + return readl(priv->io_base + offset); +} + +#define zynq_fpga_poll_timeout(priv, addr, val, cond, sleep_us, timeout_us) \ + readl_poll_timeout(priv->io_base + addr, val, cond, sleep_us, \ + timeout_us) + +/* Cause the specified irq mask bits to generate IRQs */ +static inline void zynq_fpga_set_irq(struct zynq_fpga_priv *priv, u32 enable) +{ + zynq_fpga_write(priv, INT_MASK_OFFSET, ~enable); +} + +/* Must be called with dma_lock held */ +static void zynq_step_dma(struct zynq_fpga_priv *priv) +{ + u32 addr; + u32 len; + bool first; + + first = priv->dma_elm == 0; + while (priv->cur_sg) { + /* Feed the DMA queue until it is full. */ + if (zynq_fpga_read(priv, STATUS_OFFSET) & STATUS_DMA_Q_F) + break; + + addr = sg_dma_address(priv->cur_sg); + len = sg_dma_len(priv->cur_sg); + if (priv->dma_elm + 1 == priv->dma_nelms) { + /* The last transfer waits for the PCAP to finish too, + * notice this also changes the irq_mask to ignore + * IXR_DMA_DONE_MASK which ensures we do not trigger + * the completion too early. + */ + addr |= DMA_SRC_LAST_TRANSFER; + priv->cur_sg = NULL; + } else { + priv->cur_sg = sg_next(priv->cur_sg); + priv->dma_elm++; + } + + zynq_fpga_write(priv, DMA_SRC_ADDR_OFFSET, addr); + zynq_fpga_write(priv, DMA_DST_ADDR_OFFSET, DMA_INVALID_ADDRESS); + zynq_fpga_write(priv, DMA_SRC_LEN_OFFSET, len / 4); + zynq_fpga_write(priv, DMA_DEST_LEN_OFFSET, 0); + } + + /* Once the first transfer is queued we can turn on the ISR, future + * calls to zynq_step_dma will happen from the ISR context. The + * dma_lock spinlock guarentees this handover is done coherently, the + * ISR enable is put at the end to avoid another CPU spinning in the + * ISR on this lock. + */ + if (first && priv->cur_sg) { + zynq_fpga_set_irq(priv, + IXR_DMA_DONE_MASK | IXR_ERROR_FLAGS_MASK); + } else if (!priv->cur_sg) { + /* The last transfer changes to DMA & PCAP mode since we do + * not want to continue until everything has been flushed into + * the PCAP. + */ + zynq_fpga_set_irq(priv, + IXR_D_P_DONE_MASK | IXR_ERROR_FLAGS_MASK); + } +} + +static irqreturn_t zynq_fpga_isr(int irq, void *data) +{ + struct zynq_fpga_priv *priv = data; + u32 intr_status; + + /* If anything other than DMA completion is reported stop and hand + * control back to zynq_fpga_ops_write, something went wrong, + * otherwise progress the DMA. + */ + spin_lock(&priv->dma_lock); + intr_status = zynq_fpga_read(priv, INT_STS_OFFSET); + if (!(intr_status & IXR_ERROR_FLAGS_MASK) && + (intr_status & IXR_DMA_DONE_MASK) && priv->cur_sg) { + zynq_fpga_write(priv, INT_STS_OFFSET, IXR_DMA_DONE_MASK); + zynq_step_dma(priv); + spin_unlock(&priv->dma_lock); + return IRQ_HANDLED; + } + spin_unlock(&priv->dma_lock); + + zynq_fpga_set_irq(priv, 0); + complete(&priv->dma_done); + + return IRQ_HANDLED; +} + +/* Sanity check the proposed bitstream. It must start with the sync word in + * the correct byte order, and be dword aligned. The input is a Xilinx .bin + * file with every 32 bit quantity swapped. + */ +static bool zynq_fpga_has_sync(const u8 *buf, size_t count) +{ + for (; count >= 4; buf += 4, count -= 4) + if (buf[0] == 0x66 && buf[1] == 0x55 && buf[2] == 0x99 && + buf[3] == 0xaa) + return true; + return false; +} + +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; + u32 ctrl, status; + int err; + + priv = mgr->priv; + + err = clk_enable(priv->clk); + if (err) + return err; + + /* check if bitstream is encrypted & and system's still secure */ + if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) { + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + if (!(ctrl & CTRL_SEC_EN_MASK)) { + dev_err(&mgr->dev, + "System not secure, can't use crypted bitstreams\n"); + err = -EINVAL; + goto out_err; + } + } + + /* don't globally reset PL if we're doing partial reconfig */ + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!zynq_fpga_has_sync(buf, count)) { + dev_err(&mgr->dev, + "Invalid bitstream, could not find a sync word. Bitstream must be a byte swapped .bin file\n"); + err = -EINVAL; + goto out_err; + } + + /* assert AXI interface resets */ + regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET, + FPGA_RST_ALL_MASK); + + /* disable all level shifters */ + regmap_write(priv->slcr, SLCR_LVL_SHFTR_EN_OFFSET, + LVL_SHFTR_DISABLE_ALL_MASK); + /* enable level shifters from PS to PL */ + regmap_write(priv->slcr, SLCR_LVL_SHFTR_EN_OFFSET, + LVL_SHFTR_ENABLE_PS_TO_PL); + + /* create a rising edge on PCFG_INIT. PCFG_INIT follows + * PCFG_PROG_B, so we need to poll it after setting PCFG_PROG_B + * to make sure the rising edge actually happens. + * Note: PCFG_PROG_B is low active, sequence as described in + * UG585 v1.10 page 211 + */ + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + ctrl |= CTRL_PCFG_PROG_B_MASK; + + zynq_fpga_write(priv, CTRL_OFFSET, ctrl); + + err = zynq_fpga_poll_timeout(priv, STATUS_OFFSET, status, + status & STATUS_PCFG_INIT_MASK, + INIT_POLL_DELAY, + INIT_POLL_TIMEOUT); + if (err) { + dev_err(&mgr->dev, "Timeout waiting for PCFG_INIT\n"); + goto out_err; + } + + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + ctrl &= ~CTRL_PCFG_PROG_B_MASK; + + zynq_fpga_write(priv, CTRL_OFFSET, ctrl); + + err = zynq_fpga_poll_timeout(priv, STATUS_OFFSET, status, + !(status & STATUS_PCFG_INIT_MASK), + INIT_POLL_DELAY, + INIT_POLL_TIMEOUT); + if (err) { + dev_err(&mgr->dev, "Timeout waiting for !PCFG_INIT\n"); + goto out_err; + } + + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + ctrl |= CTRL_PCFG_PROG_B_MASK; + + zynq_fpga_write(priv, CTRL_OFFSET, ctrl); + + err = zynq_fpga_poll_timeout(priv, STATUS_OFFSET, status, + status & STATUS_PCFG_INIT_MASK, + INIT_POLL_DELAY, + INIT_POLL_TIMEOUT); + if (err) { + dev_err(&mgr->dev, "Timeout waiting for PCFG_INIT\n"); + goto out_err; + } + } + + /* set configuration register with following options: + * - enable PCAP interface + * - set throughput for maximum speed (if bistream not crypted) + * - set CPU in user mode + */ + ctrl = zynq_fpga_read(priv, CTRL_OFFSET); + if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) + zynq_fpga_write(priv, CTRL_OFFSET, + (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK + | CTRL_PCAP_RATE_EN_MASK | ctrl)); + else + zynq_fpga_write(priv, CTRL_OFFSET, + (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK + | ctrl)); + + + /* We expect that the command queue is empty right now. */ + status = zynq_fpga_read(priv, STATUS_OFFSET); + if ((status & STATUS_DMA_Q_F) || + (status & STATUS_DMA_Q_E) != STATUS_DMA_Q_E) { + dev_err(&mgr->dev, "DMA command queue not right\n"); + err = -EBUSY; + goto out_err; + } + + /* ensure internal PCAP loopback is disabled */ + ctrl = zynq_fpga_read(priv, MCTRL_OFFSET); + zynq_fpga_write(priv, MCTRL_OFFSET, (~MCTRL_PCAP_LPBK_MASK & ctrl)); + + clk_disable(priv->clk); + + return 0; + +out_err: + clk_disable(priv->clk); + + return err; +} + +static int zynq_fpga_ops_write(struct fpga_manager *mgr, struct sg_table *sgt) +{ + struct zynq_fpga_priv *priv; + const char *why; + int err; + u32 intr_status; + unsigned long timeout; + unsigned long flags; + struct scatterlist *sg; + int i; + + priv = mgr->priv; + + /* The hardware can only DMA multiples of 4 bytes, and it requires the + * starting addresses to be aligned to 64 bits (UG585 pg 212). + */ + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + if ((sg->offset % 8) || (sg->length % 4)) { + dev_err(&mgr->dev, + "Invalid bitstream, chunks must be aligned\n"); + return -EINVAL; + } + } + + priv->dma_nelms = + dma_map_sg(mgr->dev.parent, sgt->sgl, sgt->nents, DMA_TO_DEVICE); + if (priv->dma_nelms == 0) { + dev_err(&mgr->dev, "Unable to DMA map (TO_DEVICE)\n"); + return -ENOMEM; + } + + /* enable clock */ + err = clk_enable(priv->clk); + if (err) + goto out_free; + + zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK); + reinit_completion(&priv->dma_done); + + /* zynq_step_dma will turn on interrupts */ + spin_lock_irqsave(&priv->dma_lock, flags); + priv->dma_elm = 0; + priv->cur_sg = sgt->sgl; + zynq_step_dma(priv); + spin_unlock_irqrestore(&priv->dma_lock, flags); + + timeout = wait_for_completion_timeout(&priv->dma_done, + msecs_to_jiffies(DMA_TIMEOUT_MS)); + + spin_lock_irqsave(&priv->dma_lock, flags); + zynq_fpga_set_irq(priv, 0); + priv->cur_sg = NULL; + spin_unlock_irqrestore(&priv->dma_lock, flags); + + intr_status = zynq_fpga_read(priv, INT_STS_OFFSET); + zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK); + + /* There doesn't seem to be a way to force cancel any DMA, so if + * something went wrong we are relying on the hardware to have halted + * the DMA before we get here, if there was we could use + * wait_for_completion_interruptible too. + */ + + if (intr_status & IXR_ERROR_FLAGS_MASK) { + why = "DMA reported error"; + err = -EIO; + goto out_report; + } + + if (priv->cur_sg || + !((intr_status & IXR_D_P_DONE_MASK) == IXR_D_P_DONE_MASK)) { + if (timeout == 0) + why = "DMA timed out"; + else + why = "DMA did not complete"; + err = -EIO; + goto out_report; + } + + err = 0; + goto out_clk; + +out_report: + dev_err(&mgr->dev, + "%s: INT_STS:0x%x CTRL:0x%x LOCK:0x%x INT_MASK:0x%x STATUS:0x%x MCTRL:0x%x\n", + why, + intr_status, + zynq_fpga_read(priv, CTRL_OFFSET), + zynq_fpga_read(priv, LOCK_OFFSET), + zynq_fpga_read(priv, INT_MASK_OFFSET), + zynq_fpga_read(priv, STATUS_OFFSET), + zynq_fpga_read(priv, MCTRL_OFFSET)); + +out_clk: + clk_disable(priv->clk); + +out_free: + dma_unmap_sg(mgr->dev.parent, sgt->sgl, sgt->nents, DMA_TO_DEVICE); + return err; +} + +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; + u32 intr_status; + + err = clk_enable(priv->clk); + if (err) + return err; + + err = zynq_fpga_poll_timeout(priv, INT_STS_OFFSET, intr_status, + intr_status & IXR_PCFG_DONE_MASK, + INIT_POLL_DELAY, + INIT_POLL_TIMEOUT); + + clk_disable(priv->clk); + + if (err) + return err; + + /* for the partial reconfig case we didn't touch the level shifters */ + 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); + + /* deassert AXI interface resets */ + regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET, + FPGA_RST_NONE_MASK); + } + + return 0; +} + +static enum fpga_mgr_states zynq_fpga_ops_state(struct fpga_manager *mgr) +{ + int err; + u32 intr_status; + struct zynq_fpga_priv *priv; + + priv = mgr->priv; + + err = clk_enable(priv->clk); + if (err) + return FPGA_MGR_STATE_UNKNOWN; + + intr_status = zynq_fpga_read(priv, INT_STS_OFFSET); + clk_disable(priv->clk); + + if (intr_status & IXR_PCFG_DONE_MASK) + return FPGA_MGR_STATE_OPERATING; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static const struct fpga_manager_ops zynq_fpga_ops = { + .initial_header_size = 128, + .state = zynq_fpga_ops_state, + .write_init = zynq_fpga_ops_write_init, + .write_sg = zynq_fpga_ops_write, + .write_complete = zynq_fpga_ops_write_complete, +}; + +static int zynq_fpga_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct zynq_fpga_priv *priv; + struct fpga_manager *mgr; + struct resource *res; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + spin_lock_init(&priv->dma_lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->io_base)) + return PTR_ERR(priv->io_base); + + 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\n"); + return PTR_ERR(priv->slcr); + } + + init_completion(&priv->dma_done); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) { + dev_err(dev, "No IRQ available\n"); + return priv->irq; + } + + priv->clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(priv->clk)) { + 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\n"); + return err; + } + + /* unlock the device */ + zynq_fpga_write(priv, UNLOCK_OFFSET, UNLOCK_MASK); + + zynq_fpga_set_irq(priv, 0); + 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); + + mgr = fpga_mgr_create(dev, "Xilinx Zynq FPGA Manager", + &zynq_fpga_ops, priv); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + + err = fpga_mgr_register(mgr); + if (err) { + dev_err(dev, "unable to register FPGA manager\n"); + fpga_mgr_free(mgr); + clk_unprepare(priv->clk); + return err; + } + + return 0; +} + +static int zynq_fpga_remove(struct platform_device *pdev) +{ + struct zynq_fpga_priv *priv; + struct fpga_manager *mgr; + + mgr = platform_get_drvdata(pdev); + priv = mgr->priv; + + fpga_mgr_unregister(mgr); + + clk_unprepare(priv->clk); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id zynq_fpga_of_match[] = { + { .compatible = "xlnx,zynq-devcfg-1.0", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, zynq_fpga_of_match); +#endif + +static struct platform_driver zynq_fpga_driver = { + .probe = zynq_fpga_probe, + .remove = zynq_fpga_remove, + .driver = { + .name = "zynq_fpga_manager", + .of_match_table = of_match_ptr(zynq_fpga_of_match), + }, +}; + +module_platform_driver(zynq_fpga_driver); + +MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>"); +MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>"); +MODULE_DESCRIPTION("Xilinx Zynq FPGA Manager"); +MODULE_LICENSE("GPL v2"); |