162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// rcpm.c - Freescale QorIQ RCPM driver 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Copyright 2019-2020 NXP 662306a36Sopenharmony_ci// 762306a36Sopenharmony_ci// Author: Ran Wang <ran.wang_1@nxp.com> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/init.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/of_address.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/suspend.h> 1562306a36Sopenharmony_ci#include <linux/kernel.h> 1662306a36Sopenharmony_ci#include <linux/acpi.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define RCPM_WAKEUP_CELL_MAX_SIZE 7 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct rcpm { 2162306a36Sopenharmony_ci unsigned int wakeup_cells; 2262306a36Sopenharmony_ci void __iomem *ippdexpcr_base; 2362306a36Sopenharmony_ci bool little_endian; 2462306a36Sopenharmony_ci}; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define SCFG_SPARECR8 0x051c 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic void copy_ippdexpcr1_setting(u32 val) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci struct device_node *np; 3162306a36Sopenharmony_ci void __iomem *regs; 3262306a36Sopenharmony_ci u32 reg_val; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "fsl,ls1021a-scfg"); 3562306a36Sopenharmony_ci if (!np) 3662306a36Sopenharmony_ci return; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci regs = of_iomap(np, 0); 3962306a36Sopenharmony_ci if (!regs) 4062306a36Sopenharmony_ci return; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci reg_val = ioread32be(regs + SCFG_SPARECR8); 4362306a36Sopenharmony_ci iowrite32be(val | reg_val, regs + SCFG_SPARECR8); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci iounmap(regs); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/** 4962306a36Sopenharmony_ci * rcpm_pm_prepare - performs device-level tasks associated with power 5062306a36Sopenharmony_ci * management, such as programming related to the wakeup source control. 5162306a36Sopenharmony_ci * @dev: Device to handle. 5262306a36Sopenharmony_ci * 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_cistatic int rcpm_pm_prepare(struct device *dev) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci int i, ret, idx; 5762306a36Sopenharmony_ci void __iomem *base; 5862306a36Sopenharmony_ci struct wakeup_source *ws; 5962306a36Sopenharmony_ci struct rcpm *rcpm; 6062306a36Sopenharmony_ci struct device_node *np = dev->of_node; 6162306a36Sopenharmony_ci u32 value[RCPM_WAKEUP_CELL_MAX_SIZE + 1]; 6262306a36Sopenharmony_ci u32 setting[RCPM_WAKEUP_CELL_MAX_SIZE] = {0}; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci rcpm = dev_get_drvdata(dev); 6562306a36Sopenharmony_ci if (!rcpm) 6662306a36Sopenharmony_ci return -EINVAL; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci base = rcpm->ippdexpcr_base; 6962306a36Sopenharmony_ci idx = wakeup_sources_read_lock(); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci /* Begin with first registered wakeup source */ 7262306a36Sopenharmony_ci for_each_wakeup_source(ws) { 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci /* skip object which is not attached to device */ 7562306a36Sopenharmony_ci if (!ws->dev || !ws->dev->parent) 7662306a36Sopenharmony_ci continue; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci ret = device_property_read_u32_array(ws->dev->parent, 7962306a36Sopenharmony_ci "fsl,rcpm-wakeup", value, 8062306a36Sopenharmony_ci rcpm->wakeup_cells + 1); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (ret) 8362306a36Sopenharmony_ci continue; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci /* 8662306a36Sopenharmony_ci * For DT mode, would handle devices with "fsl,rcpm-wakeup" 8762306a36Sopenharmony_ci * pointing to the current RCPM node. 8862306a36Sopenharmony_ci * 8962306a36Sopenharmony_ci * For ACPI mode, currently we assume there is only one 9062306a36Sopenharmony_ci * RCPM controller existing. 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ci if (is_of_node(dev->fwnode)) 9362306a36Sopenharmony_ci if (np->phandle != value[0]) 9462306a36Sopenharmony_ci continue; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* Property "#fsl,rcpm-wakeup-cells" of rcpm node defines the 9762306a36Sopenharmony_ci * number of IPPDEXPCR register cells, and "fsl,rcpm-wakeup" 9862306a36Sopenharmony_ci * of wakeup source IP contains an integer array: <phandle to 9962306a36Sopenharmony_ci * RCPM node, IPPDEXPCR0 setting, IPPDEXPCR1 setting, 10062306a36Sopenharmony_ci * IPPDEXPCR2 setting, etc>. 10162306a36Sopenharmony_ci * 10262306a36Sopenharmony_ci * So we will go thought them to collect setting data. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_ci for (i = 0; i < rcpm->wakeup_cells; i++) 10562306a36Sopenharmony_ci setting[i] |= value[i + 1]; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci wakeup_sources_read_unlock(idx); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Program all IPPDEXPCRn once */ 11162306a36Sopenharmony_ci for (i = 0; i < rcpm->wakeup_cells; i++) { 11262306a36Sopenharmony_ci u32 tmp = setting[i]; 11362306a36Sopenharmony_ci void __iomem *address = base + i * 4; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (!tmp) 11662306a36Sopenharmony_ci continue; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* We can only OR related bits */ 11962306a36Sopenharmony_ci if (rcpm->little_endian) { 12062306a36Sopenharmony_ci tmp |= ioread32(address); 12162306a36Sopenharmony_ci iowrite32(tmp, address); 12262306a36Sopenharmony_ci } else { 12362306a36Sopenharmony_ci tmp |= ioread32be(address); 12462306a36Sopenharmony_ci iowrite32be(tmp, address); 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci /* 12762306a36Sopenharmony_ci * Workaround of errata A-008646 on SoC LS1021A: 12862306a36Sopenharmony_ci * There is a bug of register ippdexpcr1. 12962306a36Sopenharmony_ci * Reading configuration register RCPM_IPPDEXPCR1 13062306a36Sopenharmony_ci * always return zero. So save ippdexpcr1's value 13162306a36Sopenharmony_ci * to register SCFG_SPARECR8.And the value of 13262306a36Sopenharmony_ci * ippdexpcr1 will be read from SCFG_SPARECR8. 13362306a36Sopenharmony_ci */ 13462306a36Sopenharmony_ci if (dev_of_node(dev) && (i == 1)) 13562306a36Sopenharmony_ci if (of_device_is_compatible(np, "fsl,ls1021a-rcpm")) 13662306a36Sopenharmony_ci copy_ippdexpcr1_setting(tmp); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return 0; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic const struct dev_pm_ops rcpm_pm_ops = { 14362306a36Sopenharmony_ci .prepare = rcpm_pm_prepare, 14462306a36Sopenharmony_ci}; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic int rcpm_probe(struct platform_device *pdev) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct device *dev = &pdev->dev; 14962306a36Sopenharmony_ci struct rcpm *rcpm; 15062306a36Sopenharmony_ci int ret; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci rcpm = devm_kzalloc(dev, sizeof(*rcpm), GFP_KERNEL); 15362306a36Sopenharmony_ci if (!rcpm) 15462306a36Sopenharmony_ci return -ENOMEM; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci rcpm->ippdexpcr_base = devm_platform_ioremap_resource(pdev, 0); 15762306a36Sopenharmony_ci if (IS_ERR(rcpm->ippdexpcr_base)) { 15862306a36Sopenharmony_ci ret = PTR_ERR(rcpm->ippdexpcr_base); 15962306a36Sopenharmony_ci return ret; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci rcpm->little_endian = device_property_read_bool( 16362306a36Sopenharmony_ci &pdev->dev, "little-endian"); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ret = device_property_read_u32(&pdev->dev, 16662306a36Sopenharmony_ci "#fsl,rcpm-wakeup-cells", &rcpm->wakeup_cells); 16762306a36Sopenharmony_ci if (ret) 16862306a36Sopenharmony_ci return ret; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci dev_set_drvdata(&pdev->dev, rcpm); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return 0; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic const struct of_device_id rcpm_of_match[] = { 17662306a36Sopenharmony_ci { .compatible = "fsl,qoriq-rcpm-2.1+", }, 17762306a36Sopenharmony_ci {} 17862306a36Sopenharmony_ci}; 17962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rcpm_of_match); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci#ifdef CONFIG_ACPI 18262306a36Sopenharmony_cistatic const struct acpi_device_id rcpm_acpi_ids[] = { 18362306a36Sopenharmony_ci {"NXP0015",}, 18462306a36Sopenharmony_ci { } 18562306a36Sopenharmony_ci}; 18662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, rcpm_acpi_ids); 18762306a36Sopenharmony_ci#endif 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic struct platform_driver rcpm_driver = { 19062306a36Sopenharmony_ci .driver = { 19162306a36Sopenharmony_ci .name = "rcpm", 19262306a36Sopenharmony_ci .of_match_table = rcpm_of_match, 19362306a36Sopenharmony_ci .acpi_match_table = ACPI_PTR(rcpm_acpi_ids), 19462306a36Sopenharmony_ci .pm = &rcpm_pm_ops, 19562306a36Sopenharmony_ci }, 19662306a36Sopenharmony_ci .probe = rcpm_probe, 19762306a36Sopenharmony_ci}; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cimodule_platform_driver(rcpm_driver); 200