GNU Linux-libre 5.10.215-gnu1
[releases.git] / drivers / soc / fsl / rcpm.c
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);