1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // rcpm.c - Freescale QorIQ RCPM driver 4 // 5 // Copyright 2019 NXP 6 // 7 // Author: Ran Wang <ran.wang_1@nxp.com> 8 9 #include <linux/init.h> 10 #include <linux/module.h> 11 #include <linux/platform_device.h> 12 #include <linux/of_address.h> 13 #include <linux/slab.h> 14 #include <linux/suspend.h> 15 #include <linux/kernel.h> 16 17 #define RCPM_WAKEUP_CELL_MAX_SIZE 7 18 19 struct rcpm { 20 unsigned int wakeup_cells; 21 void __iomem *ippdexpcr_base; 22 bool little_endian; 23 }; 24 25 /** 26 * rcpm_pm_prepare - performs device-level tasks associated with power 27 * management, such as programming related to the wakeup source control. 28 * @dev: Device to handle. 29 * 30 */ 31 static int rcpm_pm_prepare(struct device *dev) 32 { 33 int i, ret, idx; 34 void __iomem *base; 35 struct wakeup_source *ws; 36 struct rcpm *rcpm; 37 struct device_node *np = dev->of_node; 38 u32 value[RCPM_WAKEUP_CELL_MAX_SIZE + 1]; 39 u32 setting[RCPM_WAKEUP_CELL_MAX_SIZE] = {0}; 40 41 rcpm = dev_get_drvdata(dev); 42 if (!rcpm) 43 return -EINVAL; 44 45 base = rcpm->ippdexpcr_base; 46 idx = wakeup_sources_read_lock(); 47 48 /* Begin with first registered wakeup source */ 49 for_each_wakeup_source(ws) { 50 51 /* skip object which is not attached to device */ 52 if (!ws->dev || !ws->dev->parent) 53 continue; 54 55 ret = device_property_read_u32_array(ws->dev->parent, 56 "fsl,rcpm-wakeup", value, 57 rcpm->wakeup_cells + 1); 58 59 /* Wakeup source should refer to current rcpm device */ 60 if (ret || (np->phandle != value[0])) 61 continue; 62 63 /* Property "#fsl,rcpm-wakeup-cells" of rcpm node defines the 64 * number of IPPDEXPCR register cells, and "fsl,rcpm-wakeup" 65 * of wakeup source IP contains an integer array: <phandle to 66 * RCPM node, IPPDEXPCR0 setting, IPPDEXPCR1 setting, 67 * IPPDEXPCR2 setting, etc>. 68 * 69 * So we will go thought them to collect setting data. 70 */ 71 for (i = 0; i < rcpm->wakeup_cells; i++) 72 setting[i] |= value[i + 1]; 73 } 74 75 wakeup_sources_read_unlock(idx); 76 77 /* Program all IPPDEXPCRn once */ 78 for (i = 0; i < rcpm->wakeup_cells; i++) { 79 u32 tmp = setting[i]; 80 void __iomem *address = base + i * 4; 81 82 if (!tmp) 83 continue; 84 85 /* We can only OR related bits */ 86 if (rcpm->little_endian) { 87 tmp |= ioread32(address); 88 iowrite32(tmp, address); 89 } else { 90 tmp |= ioread32be(address); 91 iowrite32be(tmp, address); 92 } 93 } 94 95 return 0; 96 } 97 98 static const struct dev_pm_ops rcpm_pm_ops = { 99 .prepare = rcpm_pm_prepare, 100 }; 101 102 static int rcpm_probe(struct platform_device *pdev) 103 { 104 struct device *dev = &pdev->dev; 105 struct resource *r; 106 struct rcpm *rcpm; 107 int ret; 108 109 rcpm = devm_kzalloc(dev, sizeof(*rcpm), GFP_KERNEL); 110 if (!rcpm) 111 return -ENOMEM; 112 113 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 114 if (!r) 115 return -ENODEV; 116 117 rcpm->ippdexpcr_base = devm_ioremap_resource(&pdev->dev, r); 118 if (IS_ERR(rcpm->ippdexpcr_base)) { 119 ret = PTR_ERR(rcpm->ippdexpcr_base); 120 return ret; 121 } 122 123 rcpm->little_endian = device_property_read_bool( 124 &pdev->dev, "little-endian"); 125 126 ret = device_property_read_u32(&pdev->dev, 127 "#fsl,rcpm-wakeup-cells", &rcpm->wakeup_cells); 128 if (ret) 129 return ret; 130 131 dev_set_drvdata(&pdev->dev, rcpm); 132 133 return 0; 134 } 135 136 static const struct of_device_id rcpm_of_match[] = { 137 { .compatible = "fsl,qoriq-rcpm-2.1+", }, 138 {} 139 }; 140 MODULE_DEVICE_TABLE(of, rcpm_of_match); 141 142 static struct platform_driver rcpm_driver = { 143 .driver = { 144 .name = "rcpm", 145 .of_match_table = rcpm_of_match, 146 .pm = &rcpm_pm_ops, 147 }, 148 .probe = rcpm_probe, 149 }; 150 151 module_platform_driver(rcpm_driver); 152