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