18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// rcpm.c - Freescale QorIQ RCPM driver
48c2ecf20Sopenharmony_ci//
58c2ecf20Sopenharmony_ci// Copyright 2019 NXP
68c2ecf20Sopenharmony_ci//
78c2ecf20Sopenharmony_ci// Author: Ran Wang <ran.wang_1@nxp.com>
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/init.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/of_address.h>
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include <linux/suspend.h>
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define RCPM_WAKEUP_CELL_MAX_SIZE	7
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistruct rcpm {
208c2ecf20Sopenharmony_ci	unsigned int	wakeup_cells;
218c2ecf20Sopenharmony_ci	void __iomem	*ippdexpcr_base;
228c2ecf20Sopenharmony_ci	bool		little_endian;
238c2ecf20Sopenharmony_ci};
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/**
268c2ecf20Sopenharmony_ci * rcpm_pm_prepare - performs device-level tasks associated with power
278c2ecf20Sopenharmony_ci * management, such as programming related to the wakeup source control.
288c2ecf20Sopenharmony_ci * @dev: Device to handle.
298c2ecf20Sopenharmony_ci *
308c2ecf20Sopenharmony_ci */
318c2ecf20Sopenharmony_cistatic int rcpm_pm_prepare(struct device *dev)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	int i, ret, idx;
348c2ecf20Sopenharmony_ci	void __iomem *base;
358c2ecf20Sopenharmony_ci	struct wakeup_source	*ws;
368c2ecf20Sopenharmony_ci	struct rcpm		*rcpm;
378c2ecf20Sopenharmony_ci	struct device_node	*np = dev->of_node;
388c2ecf20Sopenharmony_ci	u32 value[RCPM_WAKEUP_CELL_MAX_SIZE + 1];
398c2ecf20Sopenharmony_ci	u32 setting[RCPM_WAKEUP_CELL_MAX_SIZE] = {0};
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	rcpm = dev_get_drvdata(dev);
428c2ecf20Sopenharmony_ci	if (!rcpm)
438c2ecf20Sopenharmony_ci		return -EINVAL;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	base = rcpm->ippdexpcr_base;
468c2ecf20Sopenharmony_ci	idx = wakeup_sources_read_lock();
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	/* Begin with first registered wakeup source */
498c2ecf20Sopenharmony_ci	for_each_wakeup_source(ws) {
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci		/* skip object which is not attached to device */
528c2ecf20Sopenharmony_ci		if (!ws->dev || !ws->dev->parent)
538c2ecf20Sopenharmony_ci			continue;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci		ret = device_property_read_u32_array(ws->dev->parent,
568c2ecf20Sopenharmony_ci				"fsl,rcpm-wakeup", value,
578c2ecf20Sopenharmony_ci				rcpm->wakeup_cells + 1);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci		/*  Wakeup source should refer to current rcpm device */
608c2ecf20Sopenharmony_ci		if (ret || (np->phandle != value[0]))
618c2ecf20Sopenharmony_ci			continue;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci		/* Property "#fsl,rcpm-wakeup-cells" of rcpm node defines the
648c2ecf20Sopenharmony_ci		 * number of IPPDEXPCR register cells, and "fsl,rcpm-wakeup"
658c2ecf20Sopenharmony_ci		 * of wakeup source IP contains an integer array: <phandle to
668c2ecf20Sopenharmony_ci		 * RCPM node, IPPDEXPCR0 setting, IPPDEXPCR1 setting,
678c2ecf20Sopenharmony_ci		 * IPPDEXPCR2 setting, etc>.
688c2ecf20Sopenharmony_ci		 *
698c2ecf20Sopenharmony_ci		 * So we will go thought them to collect setting data.
708c2ecf20Sopenharmony_ci		 */
718c2ecf20Sopenharmony_ci		for (i = 0; i < rcpm->wakeup_cells; i++)
728c2ecf20Sopenharmony_ci			setting[i] |= value[i + 1];
738c2ecf20Sopenharmony_ci	}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	wakeup_sources_read_unlock(idx);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	/* Program all IPPDEXPCRn once */
788c2ecf20Sopenharmony_ci	for (i = 0; i < rcpm->wakeup_cells; i++) {
798c2ecf20Sopenharmony_ci		u32 tmp = setting[i];
808c2ecf20Sopenharmony_ci		void __iomem *address = base + i * 4;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci		if (!tmp)
838c2ecf20Sopenharmony_ci			continue;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci		/* We can only OR related bits */
868c2ecf20Sopenharmony_ci		if (rcpm->little_endian) {
878c2ecf20Sopenharmony_ci			tmp |= ioread32(address);
888c2ecf20Sopenharmony_ci			iowrite32(tmp, address);
898c2ecf20Sopenharmony_ci		} else {
908c2ecf20Sopenharmony_ci			tmp |= ioread32be(address);
918c2ecf20Sopenharmony_ci			iowrite32be(tmp, address);
928c2ecf20Sopenharmony_ci		}
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	return 0;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic const struct dev_pm_ops rcpm_pm_ops = {
998c2ecf20Sopenharmony_ci	.prepare =  rcpm_pm_prepare,
1008c2ecf20Sopenharmony_ci};
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic int rcpm_probe(struct platform_device *pdev)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct device	*dev = &pdev->dev;
1058c2ecf20Sopenharmony_ci	struct resource *r;
1068c2ecf20Sopenharmony_ci	struct rcpm	*rcpm;
1078c2ecf20Sopenharmony_ci	int ret;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	rcpm = devm_kzalloc(dev, sizeof(*rcpm), GFP_KERNEL);
1108c2ecf20Sopenharmony_ci	if (!rcpm)
1118c2ecf20Sopenharmony_ci		return -ENOMEM;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1148c2ecf20Sopenharmony_ci	if (!r)
1158c2ecf20Sopenharmony_ci		return -ENODEV;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	rcpm->ippdexpcr_base = devm_ioremap_resource(&pdev->dev, r);
1188c2ecf20Sopenharmony_ci	if (IS_ERR(rcpm->ippdexpcr_base)) {
1198c2ecf20Sopenharmony_ci		ret =  PTR_ERR(rcpm->ippdexpcr_base);
1208c2ecf20Sopenharmony_ci		return ret;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	rcpm->little_endian = device_property_read_bool(
1248c2ecf20Sopenharmony_ci			&pdev->dev, "little-endian");
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	ret = device_property_read_u32(&pdev->dev,
1278c2ecf20Sopenharmony_ci			"#fsl,rcpm-wakeup-cells", &rcpm->wakeup_cells);
1288c2ecf20Sopenharmony_ci	if (ret)
1298c2ecf20Sopenharmony_ci		return ret;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	dev_set_drvdata(&pdev->dev, rcpm);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	return 0;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistatic const struct of_device_id rcpm_of_match[] = {
1378c2ecf20Sopenharmony_ci	{ .compatible = "fsl,qoriq-rcpm-2.1+", },
1388c2ecf20Sopenharmony_ci	{}
1398c2ecf20Sopenharmony_ci};
1408c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, rcpm_of_match);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic struct platform_driver rcpm_driver = {
1438c2ecf20Sopenharmony_ci	.driver = {
1448c2ecf20Sopenharmony_ci		.name = "rcpm",
1458c2ecf20Sopenharmony_ci		.of_match_table = rcpm_of_match,
1468c2ecf20Sopenharmony_ci		.pm	= &rcpm_pm_ops,
1478c2ecf20Sopenharmony_ci	},
1488c2ecf20Sopenharmony_ci	.probe = rcpm_probe,
1498c2ecf20Sopenharmony_ci};
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cimodule_platform_driver(rcpm_driver);
152