Commit 88b47501 authored by Olof Johansson's avatar Olof Johansson
Browse files

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

arm64: soc: ZynqMP SoC changes for v5.6

- Extend firmware interface for feature checking
- Use mailbox for communication with firmware for power management

* tag 'zynqmp-soc-for-v5.6' of https://github.com/Xilinx/linux-xlnx:
  drivers: soc: xilinx: Use mailbox IPI callback
  dt-bindings: power: reset: xilinx: Add bindings for ipi mailbox
  drivers: firmware: xilinx: Add support for feature check

Link: https://lore.kernel.org/r/f6fb26f8-b00d-a3e8-bf7d-c7ff2a8483b1@monstr.eu

Signed-off-by: default avatarOlof Johansson <olof@lixom.net>
parents 684415d0 ffdbae28
......@@ -8,9 +8,41 @@ Required properties:
- compatible: Must contain: "xlnx,zynqmp-power"
- interrupts: Interrupt specifier
-------
Example
-------
Optional properties:
- mbox-names : Name given to channels seen in the 'mboxes' property.
"tx" - Mailbox corresponding to transmit path
"rx" - Mailbox corresponding to receive path
- mboxes : Standard property to specify a Mailbox. Each value of
the mboxes property should contain a phandle to the
mailbox controller device node and an args specifier
that will be the phandle to the intended sub-mailbox
child node to be used for communication. See
Documentation/devicetree/bindings/mailbox/mailbox.txt
for more details about the generic mailbox controller
and client driver bindings. Also see
Documentation/devicetree/bindings/mailbox/ \
xlnx,zynqmp-ipi-mailbox.txt for typical controller that
is used to communicate with this System controllers.
--------
Examples
--------
Example with interrupt method:
firmware {
zynqmp_firmware: zynqmp-firmware {
compatible = "xlnx,zynqmp-firmware";
method = "smc";
zynqmp_power: zynqmp-power {
compatible = "xlnx,zynqmp-power";
interrupts = <0 35 4>;
};
};
};
Example with IPI mailbox method:
firmware {
zynqmp_firmware: zynqmp-firmware {
......@@ -19,7 +51,11 @@ firmware {
zynqmp_power: zynqmp-power {
compatible = "xlnx,zynqmp-power";
interrupt-parent = <&gic>;
interrupts = <0 35 4>;
mboxes = <&ipi_mailbox_pmu0 0>,
<&ipi_mailbox_pmu0 1>;
mbox-names = "tx", "rx";
};
};
};
......@@ -26,6 +26,9 @@
static const struct zynqmp_eemi_ops *eemi_ops_tbl;
static bool feature_check_enabled;
static u32 zynqmp_pm_features[PM_API_MAX];
static const struct mfd_cell firmware_devs[] = {
{
.name = "zynqmp_power_controller",
......@@ -44,6 +47,8 @@ static int zynqmp_pm_ret_code(u32 ret_status)
case XST_PM_SUCCESS:
case XST_PM_DOUBLE_REQ:
return 0;
case XST_PM_NO_FEATURE:
return -ENOTSUPP;
case XST_PM_NO_ACCESS:
return -EACCES;
case XST_PM_ABORT_SUSPEND:
......@@ -126,6 +131,39 @@ static noinline int do_fw_call_hvc(u64 arg0, u64 arg1, u64 arg2,
return zynqmp_pm_ret_code((enum pm_ret_status)res.a0);
}
/**
* zynqmp_pm_feature() - Check weather given feature is supported or not
* @api_id: API ID to check
*
* Return: Returns status, either success or error+reason
*/
static int zynqmp_pm_feature(u32 api_id)
{
int ret;
u32 ret_payload[PAYLOAD_ARG_CNT];
u64 smc_arg[2];
if (!feature_check_enabled)
return 0;
/* Return value if feature is already checked */
if (zynqmp_pm_features[api_id] != PM_FEATURE_UNCHECKED)
return zynqmp_pm_features[api_id];
smc_arg[0] = PM_SIP_SVC | PM_FEATURE_CHECK;
smc_arg[1] = api_id;
ret = do_fw_call(smc_arg[0], smc_arg[1], 0, ret_payload);
if (ret) {
zynqmp_pm_features[api_id] = PM_FEATURE_INVALID;
return PM_FEATURE_INVALID;
}
zynqmp_pm_features[api_id] = ret_payload[1];
return zynqmp_pm_features[api_id];
}
/**
* zynqmp_pm_invoke_fn() - Invoke the system-level platform management layer
* caller function depending on the configuration
......@@ -160,6 +198,9 @@ int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1,
*/
u64 smc_arg[4];
if (zynqmp_pm_feature(pm_api_id) == PM_FEATURE_INVALID)
return -ENOTSUPP;
smc_arg[0] = PM_SIP_SVC | pm_api_id;
smc_arg[1] = ((u64)arg1 << 32) | arg0;
smc_arg[2] = ((u64)arg3 << 32) | arg2;
......@@ -715,6 +756,8 @@ static int zynqmp_firmware_probe(struct platform_device *pdev)
np = of_find_compatible_node(NULL, NULL, "xlnx,versal");
if (!np)
return 0;
feature_check_enabled = true;
}
of_node_put(np);
......
......@@ -21,11 +21,15 @@ config ZYNQMP_POWER
bool "Enable Xilinx Zynq MPSoC Power Management driver"
depends on PM && ARCH_ZYNQMP
default y
select MAILBOX
select ZYNQMP_IPI_MBOX
help
Say yes to enable power management support for ZyqnMP SoC.
This driver uses firmware driver as an interface for power
management request to firmware. It registers isr to handle
power management callbacks from firmware.
power management callbacks from firmware. It registers mailbox client
to handle power management callbacks from firmware.
If in doubt, say N.
config ZYNQMP_PM_DOMAINS
......
......@@ -2,7 +2,7 @@
/*
* Xilinx Zynq MPSoC Power Management
*
* Copyright (C) 2014-2018 Xilinx, Inc.
* Copyright (C) 2014-2019 Xilinx, Inc.
*
* Davorin Mista <davorin.mista@aggios.com>
* Jolly Shah <jollys@xilinx.com>
......@@ -16,6 +16,21 @@
#include <linux/suspend.h>
#include <linux/firmware/xlnx-zynqmp.h>
#include <linux/mailbox/zynqmp-ipi-message.h>
/**
* struct zynqmp_pm_work_struct - Wrapper for struct work_struct
* @callback_work: Work structure
* @args: Callback arguments
*/
struct zynqmp_pm_work_struct {
struct work_struct callback_work;
u32 args[CB_ARG_CNT];
};
static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work;
static struct mbox_chan *rx_chan;
static const struct zynqmp_eemi_ops *eemi_ops;
enum pm_suspend_mode {
PM_SUSPEND_MODE_FIRST = 0,
......@@ -31,7 +46,6 @@ 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,
......@@ -68,6 +82,53 @@ static irqreturn_t zynqmp_pm_isr(int irq, void *data)
return IRQ_HANDLED;
}
static void ipi_receive_callback(struct mbox_client *cl, void *data)
{
struct zynqmp_ipi_message *msg = (struct zynqmp_ipi_message *)data;
u32 payload[CB_PAYLOAD_SIZE];
int ret;
memcpy(payload, msg->data, sizeof(msg->len));
/* First element is callback API ID, others are callback arguments */
if (payload[0] == PM_INIT_SUSPEND_CB) {
if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
return;
/* Copy callback arguments into work's structure */
memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
sizeof(zynqmp_pm_init_suspend_work->args));
queue_work(system_unbound_wq,
&zynqmp_pm_init_suspend_work->callback_work);
/* Send NULL message to mbox controller to ack the message */
ret = mbox_send_message(rx_chan, NULL);
if (ret)
pr_err("IPI ack failed. Error %d\n", ret);
}
}
/**
* zynqmp_pm_init_suspend_work_fn - Initialize suspend
* @work: Pointer to work_struct
*
* Bottom-half of PM callback IRQ handler.
*/
static void zynqmp_pm_init_suspend_work_fn(struct work_struct *work)
{
struct zynqmp_pm_work_struct *pm_work =
container_of(work, struct zynqmp_pm_work_struct, callback_work);
if (pm_work->args[0] == SUSPEND_SYSTEM_SHUTDOWN) {
orderly_poweroff(true);
} else if (pm_work->args[0] == SUSPEND_POWER_REQUEST) {
pm_suspend(PM_SUSPEND_MEM);
} else {
pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
__func__, pm_work->args[0]);
}
}
static ssize_t suspend_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
......@@ -119,6 +180,7 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
{
int ret, irq;
u32 pm_api_version;
struct mbox_client *client;
eemi_ops = zynqmp_pm_get_eemi_ops();
if (IS_ERR(eemi_ops))
......@@ -134,17 +196,46 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
if (pm_api_version < ZYNQMP_PM_VERSION)
return -ENODEV;
irq = platform_get_irq(pdev, 0);
if (irq <= 0)
return -ENXIO;
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, zynqmp_pm_isr,
IRQF_NO_SUSPEND | IRQF_ONESHOT,
dev_name(&pdev->dev), &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "devm_request_threaded_irq '%d' failed "
"with %d\n", irq, ret);
return ret;
if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) {
zynqmp_pm_init_suspend_work =
devm_kzalloc(&pdev->dev,
sizeof(struct zynqmp_pm_work_struct),
GFP_KERNEL);
if (!zynqmp_pm_init_suspend_work)
return -ENOMEM;
INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
zynqmp_pm_init_suspend_work_fn);
client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
if (!client)
return -ENOMEM;
client->dev = &pdev->dev;
client->rx_callback = ipi_receive_callback;
rx_chan = mbox_request_channel_byname(client, "rx");
if (IS_ERR(rx_chan)) {
dev_err(&pdev->dev, "Failed to request rx channel\n");
return IS_ERR(rx_chan);
}
} else if (of_find_property(pdev->dev.of_node, "interrupts", NULL)) {
irq = platform_get_irq(pdev, 0);
if (irq <= 0)
return -ENXIO;
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
zynqmp_pm_isr,
IRQF_NO_SUSPEND | IRQF_ONESHOT,
dev_name(&pdev->dev),
&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "devm_request_threaded_irq '%d' "
"failed with %d\n", irq, ret);
return ret;
}
} else {
dev_err(&pdev->dev, "Required property not found in DT node\n");
return -ENOENT;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
......@@ -160,6 +251,9 @@ static int zynqmp_pm_remove(struct platform_device *pdev)
{
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
if (!rx_chan)
mbox_free_channel(rx_chan);
return 0;
}
......
......@@ -48,6 +48,10 @@
#define ZYNQMP_PM_CAPABILITY_WAKEUP 0x4U
#define ZYNQMP_PM_CAPABILITY_UNUSABLE 0x8U
/* Feature check status */
#define PM_FEATURE_INVALID -1
#define PM_FEATURE_UNCHECKED 0
/*
* Firmware FPGA Manager flags
* XILINX_ZYNQMP_PM_FPGA_FULL: FPGA full reconfiguration
......@@ -78,11 +82,14 @@ enum pm_api_id {
PM_CLOCK_GETRATE,
PM_CLOCK_SETPARENT,
PM_CLOCK_GETPARENT,
PM_FEATURE_CHECK = 63,
PM_API_MAX,
};
/* PMU-FW return status codes */
enum pm_ret_status {
XST_PM_SUCCESS = 0,
XST_PM_NO_FEATURE = 19,
XST_PM_INTERNAL = 2000,
XST_PM_CONFLICT,
XST_PM_NO_ACCESS,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment