arm64: zynqmp: SoC changes for v5.2

- Add support for ZynqMP fpga manager
 - Defer some probes which depends on firmware driver to be ready
 - Debugfs fix
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iEYEABECAAYFAly0QlMACgkQykllyylKDCFYngCfZOByHuyiliPv+PSa6KCtCics
 ccgAn3YC1zEfvh8pmj2t0cvO/EpV2pXC
 =HBAL
 -----END PGP SIGNATURE-----

Merge tag 'zynqmp-soc-for-v5.2' of https://github.com/Xilinx/linux-xlnx into arm/drivers

arm64: zynqmp: SoC changes for v5.2

- Add support for ZynqMP fpga manager
- Defer some probes which depends on firmware driver to be ready
- Debugfs fix

* tag 'zynqmp-soc-for-v5.2' of https://github.com/Xilinx/linux-xlnx:
  fpga manager: Adding FPGA Manager support for Xilinx zynqmp
  dt-bindings: fpga: Add bindings for ZynqMP fpga driver
  firmware: xilinx: Add fpga API's
  drivers: Defer probe if firmware is not ready
  firmware: xilinx: fix debugfs write handler

Signed-off-by: Olof Johansson <olof@lixom.net>
This commit is contained in:
Olof Johansson 2019-04-28 23:07:03 -07:00
commit b94d3ff97e
14 changed files with 302 additions and 39 deletions

View file

@ -0,0 +1,25 @@
Devicetree bindings for Zynq Ultrascale MPSoC FPGA Manager.
The ZynqMP SoC uses the PCAP (Processor configuration Port) to configure the
Programmable Logic (PL). The configuration uses the firmware interface.
Required properties:
- compatible: should contain "xlnx,zynqmp-pcap-fpga"
Example for full FPGA configuration:
fpga-region0 {
compatible = "fpga-region";
fpga-mgr = <&zynqmp_pcap>;
#address-cells = <0x1>;
#size-cells = <0x1>;
};
firmware {
zynqmp_firmware: zynqmp-firmware {
compatible = "xlnx,zynqmp-firmware";
method = "smc";
zynqmp_pcap: pcap {
compatible = "xlnx,zynqmp-pcap-fpga";
};
};
};

View file

@ -41,8 +41,8 @@ Example of EEMI ops usage:
int ret;
eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops)
return -ENXIO;
if (IS_ERR(eemi_ops))
return PTR_ERR(eemi_ops);
ret = eemi_ops->query_data(qdata, ret_payload);

View file

@ -695,8 +695,8 @@ static int zynqmp_clock_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops)
return -ENXIO;
if (IS_ERR(eemi_ops))
return PTR_ERR(eemi_ops);
ret = zynqmp_clk_setup(dev->of_node);

View file

@ -90,9 +90,6 @@ static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret)
int ret;
struct zynqmp_pm_query_data qdata = {0};
if (!eemi_ops)
return -ENXIO;
switch (pm_id) {
case PM_GET_API_VERSION:
ret = eemi_ops->get_api_version(&pm_api_version);
@ -163,21 +160,14 @@ static ssize_t zynqmp_pm_debugfs_api_write(struct file *file,
strcpy(debugfs_buf, "");
if (*off != 0 || len == 0)
if (*off != 0 || len <= 1 || len > PAGE_SIZE - 1)
return -EINVAL;
kern_buff = kzalloc(len, GFP_KERNEL);
if (!kern_buff)
return -ENOMEM;
kern_buff = memdup_user_nul(ptr, len);
if (IS_ERR(kern_buff))
return PTR_ERR(kern_buff);
tmp_buff = kern_buff;
ret = strncpy_from_user(kern_buff, ptr, len);
if (ret < 0) {
ret = -EFAULT;
goto err;
}
/* Read the API name from a user request */
pm_api_req = strsep(&kern_buff, " ");

View file

@ -24,6 +24,8 @@
#include <linux/firmware/xlnx-zynqmp.h>
#include "zynqmp-debug.h"
static const struct zynqmp_eemi_ops *eemi_ops_tbl;
static const struct mfd_cell firmware_devs[] = {
{
.name = "zynqmp_power_controller",
@ -537,6 +539,49 @@ static int zynqmp_pm_reset_get_status(const enum zynqmp_pm_reset reset,
return ret;
}
/**
* zynqmp_pm_fpga_load - Perform the fpga load
* @address: Address to write to
* @size: pl bitstream size
* @flags: Bitstream type
* -XILINX_ZYNQMP_PM_FPGA_FULL: FPGA full reconfiguration
* -XILINX_ZYNQMP_PM_FPGA_PARTIAL: FPGA partial reconfiguration
*
* This function provides access to pmufw. To transfer
* the required bitstream into PL.
*
* Return: Returns status, either success or error+reason
*/
static int zynqmp_pm_fpga_load(const u64 address, const u32 size,
const u32 flags)
{
return zynqmp_pm_invoke_fn(PM_FPGA_LOAD, lower_32_bits(address),
upper_32_bits(address), size, flags, NULL);
}
/**
* zynqmp_pm_fpga_get_status - Read value from PCAP status register
* @value: Value to read
*
* This function provides access to the pmufw to get the PCAP
* status
*
* Return: Returns status, either success or error+reason
*/
static int zynqmp_pm_fpga_get_status(u32 *value)
{
u32 ret_payload[PAYLOAD_ARG_CNT];
int ret;
if (!value)
return -EINVAL;
ret = zynqmp_pm_invoke_fn(PM_FPGA_GET_STATUS, 0, 0, 0, 0, ret_payload);
*value = ret_payload[1];
return ret;
}
/**
* zynqmp_pm_init_finalize() - PM call to inform firmware that the caller
* master has initialized its own power management
@ -640,6 +685,8 @@ static const struct zynqmp_eemi_ops eemi_ops = {
.request_node = zynqmp_pm_request_node,
.release_node = zynqmp_pm_release_node,
.set_requirement = zynqmp_pm_set_requirement,
.fpga_load = zynqmp_pm_fpga_load,
.fpga_get_status = zynqmp_pm_fpga_get_status,
};
/**
@ -649,7 +696,11 @@ static const struct zynqmp_eemi_ops eemi_ops = {
*/
const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void)
{
return &eemi_ops;
if (eemi_ops_tbl)
return eemi_ops_tbl;
else
return ERR_PTR(-EPROBE_DEFER);
}
EXPORT_SYMBOL_GPL(zynqmp_pm_get_eemi_ops);
@ -694,6 +745,9 @@ static int zynqmp_firmware_probe(struct platform_device *pdev)
pr_info("%s Trustzone version v%d.%d\n", __func__,
pm_tz_version >> 16, pm_tz_version & 0xFFFF);
/* Assign eemi_ops_table */
eemi_ops_tbl = &eemi_ops;
zynqmp_pm_api_debugfs_init();
ret = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, firmware_devs,

View file

@ -204,4 +204,13 @@ config FPGA_DFL_PCI
To compile this as a module, choose M here.
config FPGA_MGR_ZYNQMP_FPGA
tristate "Xilinx ZynqMP FPGA"
depends on ARCH_ZYNQMP || COMPILE_TEST
help
FPGA manager driver support for Xilinx ZynqMP FPGAs.
This driver uses the processor configuration port(PCAP)
to configure the programmable logic(PL) through PS
on ZynqMP SoC.
endif # FPGA

View file

@ -17,6 +17,7 @@ obj-$(CONFIG_FPGA_MGR_STRATIX10_SOC) += stratix10-soc.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_FPGA_MGR_ZYNQMP_FPGA) += zynqmp-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

159
drivers/fpga/zynqmp-fpga.c Normal file
View file

@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Xilinx, Inc.
*/
#include <linux/dma-mapping.h>
#include <linux/fpga/fpga-mgr.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/string.h>
#include <linux/firmware/xlnx-zynqmp.h>
/* Constant Definitions */
#define IXR_FPGA_DONE_MASK BIT(3)
/**
* struct zynqmp_fpga_priv - Private data structure
* @dev: Device data structure
* @flags: flags which is used to identify the bitfile type
*/
struct zynqmp_fpga_priv {
struct device *dev;
u32 flags;
};
static int zynqmp_fpga_ops_write_init(struct fpga_manager *mgr,
struct fpga_image_info *info,
const char *buf, size_t size)
{
struct zynqmp_fpga_priv *priv;
priv = mgr->priv;
priv->flags = info->flags;
return 0;
}
static int zynqmp_fpga_ops_write(struct fpga_manager *mgr,
const char *buf, size_t size)
{
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
struct zynqmp_fpga_priv *priv;
dma_addr_t dma_addr;
u32 eemi_flags = 0;
char *kbuf;
int ret;
if (!eemi_ops || !eemi_ops->fpga_load)
return -ENXIO;
priv = mgr->priv;
kbuf = dma_alloc_coherent(priv->dev, size, &dma_addr, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
memcpy(kbuf, buf, size);
wmb(); /* ensure all writes are done before initiate FW call */
if (priv->flags & FPGA_MGR_PARTIAL_RECONFIG)
eemi_flags |= XILINX_ZYNQMP_PM_FPGA_PARTIAL;
ret = eemi_ops->fpga_load(dma_addr, size, eemi_flags);
dma_free_coherent(priv->dev, size, kbuf, dma_addr);
return ret;
}
static int zynqmp_fpga_ops_write_complete(struct fpga_manager *mgr,
struct fpga_image_info *info)
{
return 0;
}
static enum fpga_mgr_states zynqmp_fpga_ops_state(struct fpga_manager *mgr)
{
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
u32 status;
if (!eemi_ops || !eemi_ops->fpga_get_status)
return FPGA_MGR_STATE_UNKNOWN;
eemi_ops->fpga_get_status(&status);
if (status & IXR_FPGA_DONE_MASK)
return FPGA_MGR_STATE_OPERATING;
return FPGA_MGR_STATE_UNKNOWN;
}
static const struct fpga_manager_ops zynqmp_fpga_ops = {
.state = zynqmp_fpga_ops_state,
.write_init = zynqmp_fpga_ops_write_init,
.write = zynqmp_fpga_ops_write,
.write_complete = zynqmp_fpga_ops_write_complete,
};
static int zynqmp_fpga_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct zynqmp_fpga_priv *priv;
struct fpga_manager *mgr;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
mgr = devm_fpga_mgr_create(dev, "Xilinx ZynqMP FPGA Manager",
&zynqmp_fpga_ops, priv);
if (!mgr)
return -ENOMEM;
platform_set_drvdata(pdev, mgr);
ret = fpga_mgr_register(mgr);
if (ret) {
dev_err(dev, "unable to register FPGA manager");
return ret;
}
return 0;
}
static int zynqmp_fpga_remove(struct platform_device *pdev)
{
struct fpga_manager *mgr = platform_get_drvdata(pdev);
fpga_mgr_unregister(mgr);
return 0;
}
static const struct of_device_id zynqmp_fpga_of_match[] = {
{ .compatible = "xlnx,zynqmp-pcap-fpga", },
{},
};
MODULE_DEVICE_TABLE(of, zynqmp_fpga_of_match);
static struct platform_driver zynqmp_fpga_driver = {
.probe = zynqmp_fpga_probe,
.remove = zynqmp_fpga_remove,
.driver = {
.name = "zynqmp_fpga_manager",
.of_match_table = of_match_ptr(zynqmp_fpga_of_match),
},
};
module_platform_driver(zynqmp_fpga_driver);
MODULE_AUTHOR("Nava kishore Manne <navam@xilinx.com>");
MODULE_DESCRIPTION("Xilinx ZynqMp FPGA Manager");
MODULE_LICENSE("GPL");

View file

@ -16,6 +16,8 @@ struct zynqmp_nvmem_data {
struct nvmem_device *nvmem;
};
static const struct zynqmp_eemi_ops *eemi_ops;
static int zynqmp_nvmem_read(void *context, unsigned int offset,
void *val, size_t bytes)
{
@ -23,9 +25,7 @@ static int zynqmp_nvmem_read(void *context, unsigned int offset,
int idcode, version;
struct zynqmp_nvmem_data *priv = context;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops || !eemi_ops->get_chipid)
if (!eemi_ops->get_chipid)
return -ENXIO;
ret = eemi_ops->get_chipid(&idcode, &version);
@ -61,6 +61,10 @@ static int zynqmp_nvmem_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
eemi_ops = zynqmp_pm_get_eemi_ops();
if (IS_ERR(eemi_ops))
return PTR_ERR(eemi_ops);
priv->dev = dev;
econfig.dev = dev;
econfig.reg_read = zynqmp_nvmem_read;

View file

@ -79,11 +79,11 @@ static int zynqmp_reset_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
priv->eemi_ops = zynqmp_pm_get_eemi_ops();
if (!priv->eemi_ops)
return -ENXIO;
if (IS_ERR(priv->eemi_ops))
return PTR_ERR(priv->eemi_ops);
platform_set_drvdata(pdev, priv);
priv->rcdev.ops = &zynqmp_reset_ops;
priv->rcdev.owner = THIS_MODULE;

View file

@ -23,6 +23,8 @@
/* Flag stating if PM nodes mapped to the PM domain has been requested */
#define ZYNQMP_PM_DOMAIN_REQUESTED BIT(0)
static const struct zynqmp_eemi_ops *eemi_ops;
/**
* struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain
* @gpd: Generic power domain
@ -71,9 +73,8 @@ static int zynqmp_gpd_power_on(struct generic_pm_domain *domain)
{
int ret;
struct zynqmp_pm_domain *pd;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops || !eemi_ops->set_requirement)
if (!eemi_ops->set_requirement)
return -ENXIO;
pd = container_of(domain, struct zynqmp_pm_domain, gpd);
@ -107,9 +108,8 @@ static int zynqmp_gpd_power_off(struct generic_pm_domain *domain)
struct zynqmp_pm_domain *pd;
u32 capabilities = 0;
bool may_wakeup;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops || !eemi_ops->set_requirement)
if (!eemi_ops->set_requirement)
return -ENXIO;
pd = container_of(domain, struct zynqmp_pm_domain, gpd);
@ -160,9 +160,8 @@ static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain,
{
int ret;
struct zynqmp_pm_domain *pd;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops || !eemi_ops->request_node)
if (!eemi_ops->request_node)
return -ENXIO;
pd = container_of(domain, struct zynqmp_pm_domain, gpd);
@ -197,9 +196,8 @@ static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain,
{
int ret;
struct zynqmp_pm_domain *pd;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops || !eemi_ops->release_node)
if (!eemi_ops->release_node)
return;
pd = container_of(domain, struct zynqmp_pm_domain, gpd);
@ -266,6 +264,10 @@ static int zynqmp_gpd_probe(struct platform_device *pdev)
struct zynqmp_pm_domain *pd;
struct device *dev = &pdev->dev;
eemi_ops = zynqmp_pm_get_eemi_ops();
if (IS_ERR(eemi_ops))
return PTR_ERR(eemi_ops);
pd = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;

View file

@ -31,6 +31,7 @@ static const char *const suspend_modes[] = {
};
static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
static const struct zynqmp_eemi_ops *eemi_ops;
enum pm_api_cb_id {
PM_INIT_SUSPEND_CB = 30,
@ -92,9 +93,8 @@ static ssize_t suspend_mode_store(struct device *dev,
const char *buf, size_t count)
{
int md, ret = -EINVAL;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
if (!eemi_ops || !eemi_ops->set_suspend_mode)
if (!eemi_ops->set_suspend_mode)
return ret;
for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
@ -120,9 +120,11 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
int ret, irq;
u32 pm_api_version;
const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops();
eemi_ops = zynqmp_pm_get_eemi_ops();
if (IS_ERR(eemi_ops))
return PTR_ERR(eemi_ops);
if (!eemi_ops || !eemi_ops->get_api_version || !eemi_ops->init_finalize)
if (!eemi_ops->get_api_version || !eemi_ops->init_finalize)
return -ENXIO;
eemi_ops->init_finalize();

View file

@ -138,6 +138,7 @@
#define SPI_AUTOSUSPEND_TIMEOUT 3000
enum mode_type {GQSPI_MODE_IO, GQSPI_MODE_DMA};
static const struct zynqmp_eemi_ops *eemi_ops;
/**
* struct zynqmp_qspi - Defines qspi driver instance
@ -1021,6 +1022,10 @@ static int zynqmp_qspi_probe(struct platform_device *pdev)
struct resource *res;
struct device *dev = &pdev->dev;
eemi_ops = zynqmp_pm_get_eemi_ops();
if (IS_ERR(eemi_ops))
return PTR_ERR(eemi_ops);
master = spi_alloc_master(&pdev->dev, sizeof(*xqspi));
if (!master)
return -ENOMEM;

View file

@ -48,6 +48,14 @@
#define ZYNQMP_PM_CAPABILITY_WAKEUP 0x4U
#define ZYNQMP_PM_CAPABILITY_POWER 0x8U
/*
* Firmware FPGA Manager flags
* XILINX_ZYNQMP_PM_FPGA_FULL: FPGA full reconfiguration
* XILINX_ZYNQMP_PM_FPGA_PARTIAL: FPGA partial reconfiguration
*/
#define XILINX_ZYNQMP_PM_FPGA_FULL 0x0U
#define XILINX_ZYNQMP_PM_FPGA_PARTIAL BIT(0)
enum pm_api_id {
PM_GET_API_VERSION = 1,
PM_REQUEST_NODE = 13,
@ -56,6 +64,8 @@ enum pm_api_id {
PM_RESET_ASSERT = 17,
PM_RESET_GET_STATUS,
PM_PM_INIT_FINALIZE = 21,
PM_FPGA_LOAD,
PM_FPGA_GET_STATUS,
PM_GET_CHIPID = 24,
PM_IOCTL = 34,
PM_QUERY_DATA,
@ -258,6 +268,8 @@ struct zynqmp_pm_query_data {
struct zynqmp_eemi_ops {
int (*get_api_version)(u32 *version);
int (*get_chipid)(u32 *idcode, u32 *version);
int (*fpga_load)(const u64 address, const u32 size, const u32 flags);
int (*fpga_get_status)(u32 *value);
int (*query_data)(struct zynqmp_pm_query_data qdata, u32 *out);
int (*clock_enable)(u32 clock_id);
int (*clock_disable)(u32 clock_id);
@ -293,7 +305,7 @@ const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void);
#else
static inline struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void)
{
return NULL;
return ERR_PTR(-ENODEV);
}
#endif