drivers: soc: xilinx: Add ZynqMP PM driver

Add ZynqMP PM driver. PM driver provides power management
support for ZynqMP.

Signed-off-by: Rajan Vaja <rajan.vaja@xilinx.com>
Signed-off-by: Jolly Shah <jolly.shah@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
This commit is contained in:
Rajan Vaja 2019-01-29 12:38:21 -08:00 committed by Michal Simek
parent e178df31cf
commit ab272643d7
3 changed files with 190 additions and 0 deletions

View File

@ -17,4 +17,15 @@ config XILINX_VCU
To compile this driver as a module, choose M here: the
module will be called xlnx_vcu.
config ZYNQMP_POWER
bool "Enable Xilinx Zynq MPSoC Power Management driver"
depends on PM && ARCH_ZYNQMP
default y
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.
If in doubt, say N.
endmenu

View File

@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_XILINX_VCU) += xlnx_vcu.o
obj-$(CONFIG_ZYNQMP_POWER) += zynqmp_power.o

View File

@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Xilinx Zynq MPSoC Power Management
*
* Copyright (C) 2014-2018 Xilinx, Inc.
*
* Davorin Mista <davorin.mista@aggios.com>
* Jolly Shah <jollys@xilinx.com>
* Rajan Vaja <rajan.vaja@xilinx.com>
*/
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#include <linux/firmware/xlnx-zynqmp.h>
enum pm_suspend_mode {
PM_SUSPEND_MODE_FIRST = 0,
PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
PM_SUSPEND_MODE_POWER_OFF,
};
#define PM_SUSPEND_MODE_FIRST PM_SUSPEND_MODE_STD
static const char *const suspend_modes[] = {
[PM_SUSPEND_MODE_STD] = "standard",
[PM_SUSPEND_MODE_POWER_OFF] = "power-off",
};
static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
enum pm_api_cb_id {
PM_INIT_SUSPEND_CB = 30,
PM_ACKNOWLEDGE_CB,
PM_NOTIFY_CB,
};
static void zynqmp_pm_get_callback_data(u32 *buf)
{
zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
}
static irqreturn_t zynqmp_pm_isr(int irq, void *data)
{
u32 payload[CB_PAYLOAD_SIZE];
zynqmp_pm_get_callback_data(payload);
/* First element is callback API ID, others are callback arguments */
if (payload[0] == PM_INIT_SUSPEND_CB) {
switch (payload[1]) {
case SUSPEND_SYSTEM_SHUTDOWN:
orderly_poweroff(true);
break;
case SUSPEND_POWER_REQUEST:
pm_suspend(PM_SUSPEND_MEM);
break;
default:
pr_err("%s Unsupported InitSuspendCb reason "
"code %d\n", __func__, payload[1]);
}
}
return IRQ_HANDLED;
}
static ssize_t suspend_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
char *s = buf;
int md;
for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
if (suspend_modes[md]) {
if (md == suspend_mode)
s += sprintf(s, "[%s] ", suspend_modes[md]);
else
s += sprintf(s, "%s ", suspend_modes[md]);
}
/* Convert last space to newline */
if (s != buf)
*(s - 1) = '\n';
return (s - buf);
}
static ssize_t suspend_mode_store(struct device *dev,
struct device_attribute *attr,
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)
return ret;
for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
if (suspend_modes[md] &&
sysfs_streq(suspend_modes[md], buf)) {
ret = 0;
break;
}
if (!ret && md != suspend_mode) {
ret = eemi_ops->set_suspend_mode(md);
if (likely(!ret))
suspend_mode = md;
}
return ret ? ret : count;
}
static DEVICE_ATTR_RW(suspend_mode);
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();
if (!eemi_ops || !eemi_ops->get_api_version || !eemi_ops->init_finalize)
return -ENXIO;
eemi_ops->init_finalize();
eemi_ops->get_api_version(&pm_api_version);
/* Check PM API version number */
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;
}
ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
if (ret) {
dev_err(&pdev->dev, "unable to create sysfs interface\n");
return ret;
}
return 0;
}
static int zynqmp_pm_remove(struct platform_device *pdev)
{
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
return 0;
}
static const struct of_device_id pm_of_match[] = {
{ .compatible = "xlnx,zynqmp-power", },
{ /* end of table */ },
};
MODULE_DEVICE_TABLE(of, pm_of_match);
static struct platform_driver zynqmp_pm_platform_driver = {
.probe = zynqmp_pm_probe,
.remove = zynqmp_pm_remove,
.driver = {
.name = "zynqmp_power",
.of_match_table = pm_of_match,
},
};
module_platform_driver(zynqmp_pm_platform_driver);