1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright 2025 Duje Mihanović <duje@dujemihanovic.xyz>
4 */
5
6 #include <linux/auxiliary_bus.h>
7 #include <linux/container_of.h>
8 #include <linux/dev_printk.h>
9 #include <linux/device.h>
10 #include <linux/mfd/syscon.h>
11 #include <linux/mod_devicetable.h>
12 #include <linux/module.h>
13 #include <linux/pm_domain.h>
14 #include <linux/regmap.h>
15
16 #include <dt-bindings/power/marvell,pxa1908-power.h>
17
18 /* VPU, GPU, ISP */
19 #define APMU_PWR_CTRL_REG 0xd8
20 #define APMU_PWR_BLK_TMR_REG 0xdc
21 #define APMU_PWR_STATUS_REG 0xf0
22
23 /* DSI */
24 #define APMU_DEBUG 0x88
25 #define DSI_PHY_DVM_MASK BIT(31)
26
27 #define POWER_ON_LATENCY_US 300
28 #define POWER_OFF_LATENCY_US 20
29 #define POWER_POLL_TIMEOUT_US (25 * USEC_PER_MSEC)
30 #define POWER_POLL_SLEEP_US 6
31
32 #define NR_DOMAINS 5
33
34 #define to_pxa1908_pd(_genpd) container_of(_genpd, struct pxa1908_pd, genpd)
35
36 struct pxa1908_pd_ctrl {
37 struct generic_pm_domain *domains[NR_DOMAINS];
38 struct genpd_onecell_data onecell_data;
39 struct regmap *base;
40 struct device *dev;
41 };
42
43 struct pxa1908_pd_data {
44 u32 reg_clk_res_ctrl;
45 u32 pwr_state;
46 u32 hw_mode;
47 bool keep_on;
48 int id;
49 };
50
51 struct pxa1908_pd {
52 const struct pxa1908_pd_data data;
53 struct pxa1908_pd_ctrl *ctrl;
54 struct generic_pm_domain genpd;
55 bool initialized;
56 };
57
pxa1908_pd_is_on(struct pxa1908_pd * pd)58 static inline bool pxa1908_pd_is_on(struct pxa1908_pd *pd)
59 {
60 struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
61
62 return pd->data.id != PXA1908_POWER_DOMAIN_DSI
63 ? regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, pd->data.pwr_state)
64 : regmap_test_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
65 }
66
pxa1908_pd_power_on(struct generic_pm_domain * genpd)67 static int pxa1908_pd_power_on(struct generic_pm_domain *genpd)
68 {
69 struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
70 const struct pxa1908_pd_data *data = &pd->data;
71 struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
72 unsigned int status;
73 int ret = 0;
74
75 regmap_set_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
76 if (data->id != PXA1908_POWER_DOMAIN_ISP)
77 regmap_write(ctrl->base, APMU_PWR_BLK_TMR_REG, 0x20001fff);
78 regmap_set_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
79
80 ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
81 status & data->pwr_state, POWER_POLL_SLEEP_US,
82 POWER_ON_LATENCY_US + POWER_POLL_TIMEOUT_US);
83 if (ret == -ETIMEDOUT)
84 dev_err(ctrl->dev, "timed out powering on domain '%s'\n", pd->genpd.name);
85
86 return ret;
87 }
88
pxa1908_pd_power_off(struct generic_pm_domain * genpd)89 static int pxa1908_pd_power_off(struct generic_pm_domain *genpd)
90 {
91 struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
92 const struct pxa1908_pd_data *data = &pd->data;
93 struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
94 unsigned int status;
95 int ret;
96
97 regmap_clear_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
98
99 ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
100 !(status & data->pwr_state), POWER_POLL_SLEEP_US,
101 POWER_OFF_LATENCY_US + POWER_POLL_TIMEOUT_US);
102 if (ret == -ETIMEDOUT) {
103 dev_err(ctrl->dev, "timed out powering off domain '%s'\n", pd->genpd.name);
104 return ret;
105 }
106
107 return regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
108 }
109
pxa1908_dsi_power_on(struct generic_pm_domain * genpd)110 static inline int pxa1908_dsi_power_on(struct generic_pm_domain *genpd)
111 {
112 struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
113 struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
114
115 return regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
116 }
117
pxa1908_dsi_power_off(struct generic_pm_domain * genpd)118 static inline int pxa1908_dsi_power_off(struct generic_pm_domain *genpd)
119 {
120 struct pxa1908_pd *pd = to_pxa1908_pd(genpd);
121 struct pxa1908_pd_ctrl *ctrl = pd->ctrl;
122
123 return regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
124 }
125
126 #define DOMAIN(_id, _name, ctrl, mode, state) \
127 [_id] = { \
128 .data = { \
129 .reg_clk_res_ctrl = ctrl, \
130 .hw_mode = BIT(mode), \
131 .pwr_state = BIT(state), \
132 .id = _id, \
133 }, \
134 .genpd = { \
135 .name = _name, \
136 .power_on = pxa1908_pd_power_on, \
137 .power_off = pxa1908_pd_power_off, \
138 }, \
139 }
140
141 static struct pxa1908_pd domains[NR_DOMAINS] = {
142 DOMAIN(PXA1908_POWER_DOMAIN_VPU, "vpu", 0xa4, 19, 2),
143 DOMAIN(PXA1908_POWER_DOMAIN_GPU, "gpu", 0xcc, 11, 0),
144 DOMAIN(PXA1908_POWER_DOMAIN_GPU2D, "gpu2d", 0xf4, 11, 6),
145 DOMAIN(PXA1908_POWER_DOMAIN_ISP, "isp", 0x38, 15, 4),
146 [PXA1908_POWER_DOMAIN_DSI] = {
147 .genpd = {
148 .name = "dsi",
149 .power_on = pxa1908_dsi_power_on,
150 .power_off = pxa1908_dsi_power_off,
151 /*
152 * TODO: There is no DSI driver written yet and until then we probably
153 * don't want to power off the DSI PHY ever.
154 */
155 .flags = GENPD_FLAG_ALWAYS_ON,
156 },
157 .data = {
158 /* See above. */
159 .keep_on = true,
160 },
161 },
162 };
163
pxa1908_pd_remove(struct auxiliary_device * auxdev)164 static void pxa1908_pd_remove(struct auxiliary_device *auxdev)
165 {
166 struct pxa1908_pd *pd;
167 int ret;
168
169 for (int i = NR_DOMAINS - 1; i >= 0; i--) {
170 pd = &domains[i];
171
172 if (!pd->initialized)
173 continue;
174
175 if (pxa1908_pd_is_on(pd) && !pd->data.keep_on)
176 pxa1908_pd_power_off(&pd->genpd);
177
178 ret = pm_genpd_remove(&pd->genpd);
179 if (ret)
180 dev_err(&auxdev->dev, "failed to remove domain '%s': %d\n",
181 pd->genpd.name, ret);
182 }
183 }
184
185 static int
pxa1908_pd_init(struct pxa1908_pd_ctrl * ctrl,int id,struct device * dev)186 pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, int id, struct device *dev)
187 {
188 struct pxa1908_pd *pd = &domains[id];
189 int ret;
190
191 ctrl->domains[id] = &pd->genpd;
192
193 pd->ctrl = ctrl;
194
195 /* Make sure the state of the hardware is synced with the domain table above. */
196 if (pd->data.keep_on) {
197 ret = pd->genpd.power_on(&pd->genpd);
198 if (ret)
199 return dev_err_probe(dev, ret, "failed to power on domain '%s'\n",
200 pd->genpd.name);
201 } else {
202 if (pxa1908_pd_is_on(pd)) {
203 dev_warn(dev,
204 "domain '%s' is on despite being default off; powering off\n",
205 pd->genpd.name);
206
207 ret = pd->genpd.power_off(&pd->genpd);
208 if (ret)
209 return dev_err_probe(dev, ret,
210 "failed to power off domain '%s'\n",
211 pd->genpd.name);
212 }
213 }
214
215 ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on);
216 if (ret)
217 return dev_err_probe(dev, ret, "domain '%s' failed to initialize\n",
218 pd->genpd.name);
219
220 pd->initialized = true;
221
222 return 0;
223 }
224
225 static int
pxa1908_pd_probe(struct auxiliary_device * auxdev,const struct auxiliary_device_id * aux_id)226 pxa1908_pd_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *aux_id)
227 {
228 struct pxa1908_pd_ctrl *ctrl;
229 struct device *dev = &auxdev->dev;
230 int ret;
231
232 ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
233 if (!ctrl)
234 return -ENOMEM;
235
236 auxiliary_set_drvdata(auxdev, ctrl);
237
238 ctrl->base = syscon_node_to_regmap(dev->parent->of_node);
239 if (IS_ERR(ctrl->base))
240 return dev_err_probe(dev, PTR_ERR(ctrl->base), "no regmap available\n");
241
242 ctrl->dev = dev;
243 ctrl->onecell_data.domains = ctrl->domains;
244 ctrl->onecell_data.num_domains = NR_DOMAINS;
245
246 for (int i = 0; i < NR_DOMAINS; i++) {
247 ret = pxa1908_pd_init(ctrl, i, dev);
248 if (ret)
249 goto err;
250 }
251
252 return of_genpd_add_provider_onecell(dev->parent->of_node, &ctrl->onecell_data);
253
254 err:
255 pxa1908_pd_remove(auxdev);
256 return ret;
257 }
258
259 static const struct auxiliary_device_id pxa1908_pd_id[] = {
260 { .name = "clk_pxa1908_apmu.power" },
261 { }
262 };
263 MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id);
264
265 static struct auxiliary_driver pxa1908_pd_driver = {
266 .probe = pxa1908_pd_probe,
267 .remove = pxa1908_pd_remove,
268 .id_table = pxa1908_pd_id,
269 };
270 module_auxiliary_driver(pxa1908_pd_driver);
271
272 MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
273 MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
274 MODULE_LICENSE("GPL");
275