Skip to content

Commit

Permalink
drivers: soc: xilinx: Use mailbox IPI callback
Browse files Browse the repository at this point in the history
Add support for init suspend callback through mailbox IPI callback.

Signed-off-by: Tejas Patel <tejas.patel@xilinx.com>
Signed-off-by: Rajan Vaja <rajan.vaja@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
  • Loading branch information
Tejas Patel authored and Michal Simek committed Jan 21, 2020
1 parent a117daa commit ffdbae2
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 14 deletions.
6 changes: 5 additions & 1 deletion drivers/soc/xilinx/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
120 changes: 107 additions & 13 deletions drivers/soc/xilinx/zynqmp_power.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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))
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down

0 comments on commit ffdbae2

Please sign in to comment.