cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

zynqmp_power.c (8265B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * Xilinx Zynq MPSoC Power Management
      4 *
      5 *  Copyright (C) 2014-2019 Xilinx, Inc.
      6 *
      7 *  Davorin Mista <davorin.mista@aggios.com>
      8 *  Jolly Shah <jollys@xilinx.com>
      9 *  Rajan Vaja <rajan.vaja@xilinx.com>
     10 */
     11
     12#include <linux/mailbox_client.h>
     13#include <linux/module.h>
     14#include <linux/platform_device.h>
     15#include <linux/reboot.h>
     16#include <linux/suspend.h>
     17
     18#include <linux/firmware/xlnx-zynqmp.h>
     19#include <linux/firmware/xlnx-event-manager.h>
     20#include <linux/mailbox/zynqmp-ipi-message.h>
     21
     22/**
     23 * struct zynqmp_pm_work_struct - Wrapper for struct work_struct
     24 * @callback_work:	Work structure
     25 * @args:		Callback arguments
     26 */
     27struct zynqmp_pm_work_struct {
     28	struct work_struct callback_work;
     29	u32 args[CB_ARG_CNT];
     30};
     31
     32static struct zynqmp_pm_work_struct *zynqmp_pm_init_suspend_work;
     33static struct mbox_chan *rx_chan;
     34static bool event_registered;
     35
     36enum pm_suspend_mode {
     37	PM_SUSPEND_MODE_FIRST = 0,
     38	PM_SUSPEND_MODE_STD = PM_SUSPEND_MODE_FIRST,
     39	PM_SUSPEND_MODE_POWER_OFF,
     40};
     41
     42#define PM_SUSPEND_MODE_FIRST	PM_SUSPEND_MODE_STD
     43
     44static const char *const suspend_modes[] = {
     45	[PM_SUSPEND_MODE_STD] = "standard",
     46	[PM_SUSPEND_MODE_POWER_OFF] = "power-off",
     47};
     48
     49static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD;
     50
     51static void zynqmp_pm_get_callback_data(u32 *buf)
     52{
     53	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
     54}
     55
     56static void suspend_event_callback(const u32 *payload, void *data)
     57{
     58	/* First element is callback API ID, others are callback arguments */
     59	if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
     60		return;
     61
     62	/* Copy callback arguments into work's structure */
     63	memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
     64	       sizeof(zynqmp_pm_init_suspend_work->args));
     65
     66	queue_work(system_unbound_wq, &zynqmp_pm_init_suspend_work->callback_work);
     67}
     68
     69static irqreturn_t zynqmp_pm_isr(int irq, void *data)
     70{
     71	u32 payload[CB_PAYLOAD_SIZE];
     72
     73	zynqmp_pm_get_callback_data(payload);
     74
     75	/* First element is callback API ID, others are callback arguments */
     76	if (payload[0] == PM_INIT_SUSPEND_CB) {
     77		switch (payload[1]) {
     78		case SUSPEND_SYSTEM_SHUTDOWN:
     79			orderly_poweroff(true);
     80			break;
     81		case SUSPEND_POWER_REQUEST:
     82			pm_suspend(PM_SUSPEND_MEM);
     83			break;
     84		default:
     85			pr_err("%s Unsupported InitSuspendCb reason "
     86				"code %d\n", __func__, payload[1]);
     87		}
     88	}
     89
     90	return IRQ_HANDLED;
     91}
     92
     93static void ipi_receive_callback(struct mbox_client *cl, void *data)
     94{
     95	struct zynqmp_ipi_message *msg = (struct zynqmp_ipi_message *)data;
     96	u32 payload[CB_PAYLOAD_SIZE];
     97	int ret;
     98
     99	memcpy(payload, msg->data, sizeof(msg->len));
    100	/* First element is callback API ID, others are callback arguments */
    101	if (payload[0] == PM_INIT_SUSPEND_CB) {
    102		if (work_pending(&zynqmp_pm_init_suspend_work->callback_work))
    103			return;
    104
    105		/* Copy callback arguments into work's structure */
    106		memcpy(zynqmp_pm_init_suspend_work->args, &payload[1],
    107		       sizeof(zynqmp_pm_init_suspend_work->args));
    108
    109		queue_work(system_unbound_wq,
    110			   &zynqmp_pm_init_suspend_work->callback_work);
    111
    112		/* Send NULL message to mbox controller to ack the message */
    113		ret = mbox_send_message(rx_chan, NULL);
    114		if (ret)
    115			pr_err("IPI ack failed. Error %d\n", ret);
    116	}
    117}
    118
    119/**
    120 * zynqmp_pm_init_suspend_work_fn - Initialize suspend
    121 * @work:	Pointer to work_struct
    122 *
    123 * Bottom-half of PM callback IRQ handler.
    124 */
    125static void zynqmp_pm_init_suspend_work_fn(struct work_struct *work)
    126{
    127	struct zynqmp_pm_work_struct *pm_work =
    128		container_of(work, struct zynqmp_pm_work_struct, callback_work);
    129
    130	if (pm_work->args[0] == SUSPEND_SYSTEM_SHUTDOWN) {
    131		orderly_poweroff(true);
    132	} else if (pm_work->args[0] == SUSPEND_POWER_REQUEST) {
    133		pm_suspend(PM_SUSPEND_MEM);
    134	} else {
    135		pr_err("%s Unsupported InitSuspendCb reason code %d.\n",
    136		       __func__, pm_work->args[0]);
    137	}
    138}
    139
    140static ssize_t suspend_mode_show(struct device *dev,
    141				 struct device_attribute *attr, char *buf)
    142{
    143	char *s = buf;
    144	int md;
    145
    146	for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
    147		if (suspend_modes[md]) {
    148			if (md == suspend_mode)
    149				s += sprintf(s, "[%s] ", suspend_modes[md]);
    150			else
    151				s += sprintf(s, "%s ", suspend_modes[md]);
    152		}
    153
    154	/* Convert last space to newline */
    155	if (s != buf)
    156		*(s - 1) = '\n';
    157	return (s - buf);
    158}
    159
    160static ssize_t suspend_mode_store(struct device *dev,
    161				  struct device_attribute *attr,
    162				  const char *buf, size_t count)
    163{
    164	int md, ret = -EINVAL;
    165
    166	for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++)
    167		if (suspend_modes[md] &&
    168		    sysfs_streq(suspend_modes[md], buf)) {
    169			ret = 0;
    170			break;
    171		}
    172
    173	if (!ret && md != suspend_mode) {
    174		ret = zynqmp_pm_set_suspend_mode(md);
    175		if (likely(!ret))
    176			suspend_mode = md;
    177	}
    178
    179	return ret ? ret : count;
    180}
    181
    182static DEVICE_ATTR_RW(suspend_mode);
    183
    184static int zynqmp_pm_probe(struct platform_device *pdev)
    185{
    186	int ret, irq;
    187	u32 pm_api_version;
    188	struct mbox_client *client;
    189
    190	zynqmp_pm_get_api_version(&pm_api_version);
    191
    192	/* Check PM API version number */
    193	if (pm_api_version < ZYNQMP_PM_VERSION)
    194		return -ENODEV;
    195
    196	/*
    197	 * First try to use Xilinx Event Manager by registering suspend_event_callback
    198	 * for suspend/shutdown event.
    199	 * If xlnx_register_event() returns -EACCES (Xilinx Event Manager
    200	 * is not available to use) or -ENODEV(Xilinx Event Manager not compiled),
    201	 * then use ipi-mailbox or interrupt method.
    202	 */
    203	ret = xlnx_register_event(PM_INIT_SUSPEND_CB, 0, 0, false,
    204				  suspend_event_callback, NULL);
    205	if (!ret) {
    206		zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
    207							   sizeof(struct zynqmp_pm_work_struct),
    208							   GFP_KERNEL);
    209		if (!zynqmp_pm_init_suspend_work) {
    210			xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0,
    211					      suspend_event_callback, NULL);
    212			return -ENOMEM;
    213		}
    214		event_registered = true;
    215
    216		INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
    217			  zynqmp_pm_init_suspend_work_fn);
    218	} else if (ret != -EACCES && ret != -ENODEV) {
    219		dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
    220		return ret;
    221	} else if (of_find_property(pdev->dev.of_node, "mboxes", NULL)) {
    222		zynqmp_pm_init_suspend_work =
    223			devm_kzalloc(&pdev->dev,
    224				     sizeof(struct zynqmp_pm_work_struct),
    225				     GFP_KERNEL);
    226		if (!zynqmp_pm_init_suspend_work)
    227			return -ENOMEM;
    228
    229		INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
    230			  zynqmp_pm_init_suspend_work_fn);
    231		client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
    232		if (!client)
    233			return -ENOMEM;
    234
    235		client->dev = &pdev->dev;
    236		client->rx_callback = ipi_receive_callback;
    237
    238		rx_chan = mbox_request_channel_byname(client, "rx");
    239		if (IS_ERR(rx_chan)) {
    240			dev_err(&pdev->dev, "Failed to request rx channel\n");
    241			return PTR_ERR(rx_chan);
    242		}
    243	} else if (of_find_property(pdev->dev.of_node, "interrupts", NULL)) {
    244		irq = platform_get_irq(pdev, 0);
    245		if (irq <= 0)
    246			return -ENXIO;
    247
    248		ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
    249						zynqmp_pm_isr,
    250						IRQF_NO_SUSPEND | IRQF_ONESHOT,
    251						dev_name(&pdev->dev),
    252						&pdev->dev);
    253		if (ret) {
    254			dev_err(&pdev->dev, "devm_request_threaded_irq '%d' "
    255					    "failed with %d\n", irq, ret);
    256			return ret;
    257		}
    258	} else {
    259		dev_err(&pdev->dev, "Required property not found in DT node\n");
    260		return -ENOENT;
    261	}
    262
    263	ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
    264	if (ret) {
    265		if (event_registered) {
    266			xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0, suspend_event_callback,
    267					      NULL);
    268			event_registered = false;
    269		}
    270		dev_err(&pdev->dev, "unable to create sysfs interface\n");
    271		return ret;
    272	}
    273
    274	return 0;
    275}
    276
    277static int zynqmp_pm_remove(struct platform_device *pdev)
    278{
    279	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
    280	if (event_registered)
    281		xlnx_unregister_event(PM_INIT_SUSPEND_CB, 0, 0, suspend_event_callback, NULL);
    282
    283	if (!rx_chan)
    284		mbox_free_channel(rx_chan);
    285
    286	return 0;
    287}
    288
    289static const struct of_device_id pm_of_match[] = {
    290	{ .compatible = "xlnx,zynqmp-power", },
    291	{ /* end of table */ },
    292};
    293MODULE_DEVICE_TABLE(of, pm_of_match);
    294
    295static struct platform_driver zynqmp_pm_platform_driver = {
    296	.probe = zynqmp_pm_probe,
    297	.remove = zynqmp_pm_remove,
    298	.driver = {
    299		.name = "zynqmp_power",
    300		.of_match_table = pm_of_match,
    301	},
    302};
    303module_platform_driver(zynqmp_pm_platform_driver);