Skip to content

Commit

Permalink
firmware: xilinx: Add Zynqmp firmware driver
Browse files Browse the repository at this point in the history
This patch is adding communication layer with firmware.
Firmware driver provides an interface to firmware APIs.
Interface APIs can be used by any driver to communicate to
PMUFW(Platform Management Unit). All requests go through ATF.

Signed-off-by: Rajan Vaja <rajanv@xilinx.com>
Signed-off-by: Jolly Shah <jollys@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
  • Loading branch information
Rajan Vaja authored and Michal Simek committed Sep 26, 2018
1 parent 95bf69a commit 7658267
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 0 deletions.
1 change: 1 addition & 0 deletions arch/arm64/Kconfig.platforms
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ config ARCH_ZX

config ARCH_ZYNQMP
bool "Xilinx ZynqMP Family"
select ZYNQMP_FIRMWARE
help
This enables support for Xilinx ZynqMP Family

Expand Down
1 change: 1 addition & 0 deletions drivers/firmware/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -291,5 +291,6 @@ source "drivers/firmware/google/Kconfig"
source "drivers/firmware/efi/Kconfig"
source "drivers/firmware/meson/Kconfig"
source "drivers/firmware/tegra/Kconfig"
source "drivers/firmware/xilinx/Kconfig"

endmenu
1 change: 1 addition & 0 deletions drivers/firmware/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
obj-$(CONFIG_EFI) += efi/
obj-$(CONFIG_UEFI_CPER) += efi/
obj-y += tegra/
obj-y += xilinx/
16 changes: 16 additions & 0 deletions drivers/firmware/xilinx/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0
# Kconfig for Xilinx firmwares

menu "Zynq MPSoC Firmware Drivers"
depends on ARCH_ZYNQMP

config ZYNQMP_FIRMWARE
bool "Enable Xilinx Zynq MPSoC firmware interface"
help
Firmware interface driver is used by different
drivers to communicate with the firmware for
various platform management services.
Say yes to enable ZynqMP firmware interface driver.
If in doubt, say N.

endmenu
4 changes: 4 additions & 0 deletions drivers/firmware/xilinx/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Xilinx firmwares

obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o
322 changes: 322 additions & 0 deletions drivers/firmware/xilinx/zynqmp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Xilinx Zynq MPSoC Firmware layer
*
* Copyright (C) 2014-2018 Xilinx, Inc.
*
* Michal Simek <michal.simek@xilinx.com>
* Davorin Mista <davorin.mista@aggios.com>
* Jolly Shah <jollys@xilinx.com>
* Rajan Vaja <rajanv@xilinx.com>
*/

#include <linux/arm-smccc.h>
#include <linux/compiler.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#include <linux/firmware/xlnx-zynqmp.h>

/**
* zynqmp_pm_ret_code() - Convert PMU-FW error codes to Linux error codes
* @ret_status: PMUFW return code
*
* Return: corresponding Linux error code
*/
static int zynqmp_pm_ret_code(u32 ret_status)
{
switch (ret_status) {
case XST_PM_SUCCESS:
case XST_PM_DOUBLE_REQ:
return 0;
case XST_PM_NO_ACCESS:
return -EACCES;
case XST_PM_ABORT_SUSPEND:
return -ECANCELED;
case XST_PM_INTERNAL:
case XST_PM_CONFLICT:
case XST_PM_INVALID_NODE:
default:
return -EINVAL;
}
}

static noinline int do_fw_call_fail(u64 arg0, u64 arg1, u64 arg2,
u32 *ret_payload)
{
return -ENODEV;
}

/*
* PM function call wrapper
* Invoke do_fw_call_smc or do_fw_call_hvc, depending on the configuration
*/
static int (*do_fw_call)(u64, u64, u64, u32 *ret_payload) = do_fw_call_fail;

/**
* do_fw_call_smc() - Call system-level platform management layer (SMC)
* @arg0: Argument 0 to SMC call
* @arg1: Argument 1 to SMC call
* @arg2: Argument 2 to SMC call
* @ret_payload: Returned value array
*
* Invoke platform management function via SMC call (no hypervisor present).
*
* Return: Returns status, either success or error+reason
*/
static noinline int do_fw_call_smc(u64 arg0, u64 arg1, u64 arg2,
u32 *ret_payload)
{
struct arm_smccc_res res;

arm_smccc_smc(arg0, arg1, arg2, 0, 0, 0, 0, 0, &res);

if (ret_payload) {
ret_payload[0] = lower_32_bits(res.a0);
ret_payload[1] = upper_32_bits(res.a0);
ret_payload[2] = lower_32_bits(res.a1);
ret_payload[3] = upper_32_bits(res.a1);
}

return zynqmp_pm_ret_code((enum pm_ret_status)res.a0);
}

/**
* do_fw_call_hvc() - Call system-level platform management layer (HVC)
* @arg0: Argument 0 to HVC call
* @arg1: Argument 1 to HVC call
* @arg2: Argument 2 to HVC call
* @ret_payload: Returned value array
*
* Invoke platform management function via HVC
* HVC-based for communication through hypervisor
* (no direct communication with ATF).
*
* Return: Returns status, either success or error+reason
*/
static noinline int do_fw_call_hvc(u64 arg0, u64 arg1, u64 arg2,
u32 *ret_payload)
{
struct arm_smccc_res res;

arm_smccc_hvc(arg0, arg1, arg2, 0, 0, 0, 0, 0, &res);

if (ret_payload) {
ret_payload[0] = lower_32_bits(res.a0);
ret_payload[1] = upper_32_bits(res.a0);
ret_payload[2] = lower_32_bits(res.a1);
ret_payload[3] = upper_32_bits(res.a1);
}

return zynqmp_pm_ret_code((enum pm_ret_status)res.a0);
}

/**
* zynqmp_pm_invoke_fn() - Invoke the system-level platform management layer
* caller function depending on the configuration
* @pm_api_id: Requested PM-API call
* @arg0: Argument 0 to requested PM-API call
* @arg1: Argument 1 to requested PM-API call
* @arg2: Argument 2 to requested PM-API call
* @arg3: Argument 3 to requested PM-API call
* @ret_payload: Returned value array
*
* Invoke platform management function for SMC or HVC call, depending on
* configuration.
* Following SMC Calling Convention (SMCCC) for SMC64:
* Pm Function Identifier,
* PM_SIP_SVC + PM_API_ID =
* ((SMC_TYPE_FAST << FUNCID_TYPE_SHIFT)
* ((SMC_64) << FUNCID_CC_SHIFT)
* ((SIP_START) << FUNCID_OEN_SHIFT)
* ((PM_API_ID) & FUNCID_NUM_MASK))
*
* PM_SIP_SVC - Registered ZynqMP SIP Service Call.
* PM_API_ID - Platform Management API ID.
*
* Return: Returns status, either success or error+reason
*/
int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1,
u32 arg2, u32 arg3, u32 *ret_payload)
{
/*
* Added SIP service call Function Identifier
* Make sure to stay in x0 register
*/
u64 smc_arg[4];

smc_arg[0] = PM_SIP_SVC | pm_api_id;
smc_arg[1] = ((u64)arg1 << 32) | arg0;
smc_arg[2] = ((u64)arg3 << 32) | arg2;

return do_fw_call(smc_arg[0], smc_arg[1], smc_arg[2], ret_payload);
}

static u32 pm_api_version;
static u32 pm_tz_version;

/**
* zynqmp_pm_get_api_version() - Get version number of PMU PM firmware
* @version: Returned version value
*
* Return: Returns status, either success or error+reason
*/
static int zynqmp_pm_get_api_version(u32 *version)
{
u32 ret_payload[PAYLOAD_ARG_CNT];
int ret;

if (!version)
return -EINVAL;

/* Check is PM API version already verified */
if (pm_api_version > 0) {
*version = pm_api_version;
return 0;
}
ret = zynqmp_pm_invoke_fn(PM_GET_API_VERSION, 0, 0, 0, 0, ret_payload);
*version = ret_payload[1];

return ret;
}

/**
* zynqmp_pm_get_trustzone_version() - Get secure trustzone firmware version
* @version: Returned version value
*
* Return: Returns status, either success or error+reason
*/
static int zynqmp_pm_get_trustzone_version(u32 *version)
{
u32 ret_payload[PAYLOAD_ARG_CNT];
int ret;

if (!version)
return -EINVAL;

/* Check is PM trustzone version already verified */
if (pm_tz_version > 0) {
*version = pm_tz_version;
return 0;
}
ret = zynqmp_pm_invoke_fn(PM_GET_TRUSTZONE_VERSION, 0, 0,
0, 0, ret_payload);
*version = ret_payload[1];

return ret;
}

/**
* get_set_conduit_method() - Choose SMC or HVC based communication
* @np: Pointer to the device_node structure
*
* Use SMC or HVC-based functions to communicate with EL2/EL3.
*
* Return: Returns 0 on success or error code
*/
static int get_set_conduit_method(struct device_node *np)
{
const char *method;

if (of_property_read_string(np, "method", &method)) {
pr_warn("%s missing \"method\" property\n", __func__);
return -ENXIO;
}

if (!strcmp("hvc", method)) {
do_fw_call = do_fw_call_hvc;
} else if (!strcmp("smc", method)) {
do_fw_call = do_fw_call_smc;
} else {
pr_warn("%s Invalid \"method\" property: %s\n",
__func__, method);
return -EINVAL;
}

return 0;
}

static const struct zynqmp_eemi_ops eemi_ops = {
.get_api_version = zynqmp_pm_get_api_version,
};

/**
* zynqmp_pm_get_eemi_ops - Get eemi ops functions
*
* Return: Pointer of eemi_ops structure
*/
const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void)
{
return &eemi_ops;
}
EXPORT_SYMBOL_GPL(zynqmp_pm_get_eemi_ops);

static int zynqmp_firmware_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np;
int ret;

np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp");
if (!np)
return 0;
of_node_put(np);

ret = get_set_conduit_method(dev->of_node);
if (ret)
return ret;

/* Check PM API version number */
zynqmp_pm_get_api_version(&pm_api_version);
if (pm_api_version < ZYNQMP_PM_VERSION) {
panic("%s Platform Management API version error. Expected: v%d.%d - Found: v%d.%d\n",
__func__,
ZYNQMP_PM_VERSION_MAJOR, ZYNQMP_PM_VERSION_MINOR,
pm_api_version >> 16, pm_api_version & 0xFFFF);
}

pr_info("%s Platform Management API v%d.%d\n", __func__,
pm_api_version >> 16, pm_api_version & 0xFFFF);

/* Check trustzone version number */
ret = zynqmp_pm_get_trustzone_version(&pm_tz_version);
if (ret)
panic("Legacy trustzone found without version support\n");

if (pm_tz_version < ZYNQMP_TZ_VERSION)
panic("%s Trustzone version error. Expected: v%d.%d - Found: v%d.%d\n",
__func__,
ZYNQMP_TZ_VERSION_MAJOR, ZYNQMP_TZ_VERSION_MINOR,
pm_tz_version >> 16, pm_tz_version & 0xFFFF);

pr_info("%s Trustzone version v%d.%d\n", __func__,
pm_tz_version >> 16, pm_tz_version & 0xFFFF);

return of_platform_populate(dev->of_node, NULL, NULL, dev);
}

static int zynqmp_firmware_remove(struct platform_device *pdev)
{
return 0;
}

static const struct of_device_id zynqmp_firmware_of_match[] = {
{.compatible = "xlnx,zynqmp-firmware"},
{},
};
MODULE_DEVICE_TABLE(of, zynqmp_firmware_of_match);

static struct platform_driver zynqmp_firmware_driver = {
.driver = {
.name = "zynqmp_firmware",
.of_match_table = zynqmp_firmware_of_match,
},
.probe = zynqmp_firmware_probe,
.remove = zynqmp_firmware_remove,
};
module_platform_driver(zynqmp_firmware_driver);
Loading

0 comments on commit 7658267

Please sign in to comment.